summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging')
-rw-r--r--src/com/android/messaging/BugleApplication.java262
-rw-r--r--src/com/android/messaging/Factory.java75
-rw-r--r--src/com/android/messaging/FactoryImpl.java245
-rw-r--r--src/com/android/messaging/annotation/VisibleForAnimation.java23
-rw-r--r--src/com/android/messaging/datamodel/BitmapPool.java364
-rw-r--r--src/com/android/messaging/datamodel/BoundCursorLoader.java46
-rw-r--r--src/com/android/messaging/datamodel/BugleDatabaseOperations.java1919
-rw-r--r--src/com/android/messaging/datamodel/BugleNotifications.java1221
-rw-r--r--src/com/android/messaging/datamodel/BugleRecipientEntry.java64
-rw-r--r--src/com/android/messaging/datamodel/ConversationImagePartsView.java120
-rw-r--r--src/com/android/messaging/datamodel/CursorQueryData.java82
-rw-r--r--src/com/android/messaging/datamodel/DataModel.java158
-rw-r--r--src/com/android/messaging/datamodel/DataModelException.java101
-rw-r--r--src/com/android/messaging/datamodel/DataModelImpl.java236
-rw-r--r--src/com/android/messaging/datamodel/DatabaseHelper.java813
-rw-r--r--src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java42
-rw-r--r--src/com/android/messaging/datamodel/DatabaseWrapper.java482
-rw-r--r--src/com/android/messaging/datamodel/FileProvider.java151
-rw-r--r--src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java179
-rw-r--r--src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java114
-rw-r--r--src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java49
-rw-r--r--src/com/android/messaging/datamodel/MediaScratchFileProvider.java132
-rw-r--r--src/com/android/messaging/datamodel/MemoryCacheManager.java75
-rw-r--r--src/com/android/messaging/datamodel/MessageNotificationState.java1342
-rw-r--r--src/com/android/messaging/datamodel/MessageTextStats.java92
-rw-r--r--src/com/android/messaging/datamodel/MessagingContentProvider.java476
-rw-r--r--src/com/android/messaging/datamodel/MmsFileProvider.java69
-rw-r--r--src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java148
-rw-r--r--src/com/android/messaging/datamodel/NotificationState.java149
-rw-r--r--src/com/android/messaging/datamodel/ParticipantRefresh.java738
-rw-r--r--src/com/android/messaging/datamodel/SyncManager.java478
-rw-r--r--src/com/android/messaging/datamodel/action/Action.java295
-rw-r--r--src/com/android/messaging/datamodel/action/ActionMonitor.java477
-rw-r--r--src/com/android/messaging/datamodel/action/ActionService.java63
-rw-r--r--src/com/android/messaging/datamodel/action/ActionServiceImpl.java341
-rw-r--r--src/com/android/messaging/datamodel/action/BackgroundWorker.java32
-rw-r--r--src/com/android/messaging/datamodel/action/BackgroundWorkerService.java168
-rw-r--r--src/com/android/messaging/datamodel/action/BugleActionToasts.java172
-rw-r--r--src/com/android/messaging/datamodel/action/DeleteConversationAction.java205
-rw-r--r--src/com/android/messaging/datamodel/action/DeleteMessageAction.java135
-rw-r--r--src/com/android/messaging/datamodel/action/DownloadMmsAction.java340
-rw-r--r--src/com/android/messaging/datamodel/action/DumpDatabaseAction.java124
-rw-r--r--src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java114
-rw-r--r--src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java173
-rw-r--r--src/com/android/messaging/datamodel/action/HandleLowStorageAction.java94
-rw-r--r--src/com/android/messaging/datamodel/action/InsertNewMessageAction.java480
-rw-r--r--src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java153
-rw-r--r--src/com/android/messaging/datamodel/action/MarkAsReadAction.java113
-rw-r--r--src/com/android/messaging/datamodel/action/MarkAsSeenAction.java126
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java122
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java573
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java470
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java310
-rw-r--r--src/com/android/messaging/datamodel/action/ReadDraftDataAction.java166
-rw-r--r--src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java197
-rw-r--r--src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java198
-rw-r--r--src/com/android/messaging/datamodel/action/RedownloadMmsAction.java128
-rw-r--r--src/com/android/messaging/datamodel/action/ResendMessageAction.java128
-rw-r--r--src/com/android/messaging/datamodel/action/SendMessageAction.java447
-rw-r--r--src/com/android/messaging/datamodel/action/SyncCursorPair.java712
-rw-r--r--src/com/android/messaging/datamodel/action/SyncMessageBatch.java383
-rw-r--r--src/com/android/messaging/datamodel/action/SyncMessagesAction.java637
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java93
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java156
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java148
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java63
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java103
-rw-r--r--src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java104
-rw-r--r--src/com/android/messaging/datamodel/binding/BindableData.java72
-rw-r--r--src/com/android/messaging/datamodel/binding/BindableOnceData.java42
-rw-r--r--src/com/android/messaging/datamodel/binding/Binding.java94
-rw-r--r--src/com/android/messaging/datamodel/binding/BindingBase.java84
-rw-r--r--src/com/android/messaging/datamodel/binding/DetachableBinding.java56
-rw-r--r--src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java83
-rw-r--r--src/com/android/messaging/datamodel/data/BlockedParticipantsData.java103
-rw-r--r--src/com/android/messaging/datamodel/data/ContactListItemData.java160
-rw-r--r--src/com/android/messaging/datamodel/data/ContactPickerData.java194
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationData.java849
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationListData.java211
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationListItemData.java510
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java37
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationMessageData.java917
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationParticipantsData.java125
-rw-r--r--src/com/android/messaging/datamodel/data/DraftMessageData.java855
-rw-r--r--src/com/android/messaging/datamodel/data/GalleryGridItemData.java128
-rw-r--r--src/com/android/messaging/datamodel/data/LaunchConversationData.java90
-rw-r--r--src/com/android/messaging/datamodel/data/MediaPickerData.java175
-rw-r--r--src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java64
-rw-r--r--src/com/android/messaging/datamodel/data/MessageData.java922
-rw-r--r--src/com/android/messaging/datamodel/data/MessagePartData.java534
-rw-r--r--src/com/android/messaging/datamodel/data/ParticipantData.java569
-rw-r--r--src/com/android/messaging/datamodel/data/ParticipantListItemData.java95
-rw-r--r--src/com/android/messaging/datamodel/data/PendingAttachmentData.java176
-rw-r--r--src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java210
-rw-r--r--src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java155
-rw-r--r--src/com/android/messaging/datamodel/data/PersonItemData.java67
-rw-r--r--src/com/android/messaging/datamodel/data/SelfParticipantsData.java107
-rw-r--r--src/com/android/messaging/datamodel/data/SettingsData.java223
-rw-r--r--src/com/android/messaging/datamodel/data/SubscriptionListData.java128
-rw-r--r--src/com/android/messaging/datamodel/data/VCardContactItemData.java185
-rw-r--r--src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java74
-rw-r--r--src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java159
-rw-r--r--src/com/android/messaging/datamodel/media/AvatarRequest.java189
-rw-r--r--src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java60
-rw-r--r--src/com/android/messaging/datamodel/media/BindableMediaRequest.java63
-rw-r--r--src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java54
-rw-r--r--src/com/android/messaging/datamodel/media/CompositeImageRequest.java109
-rw-r--r--src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java61
-rw-r--r--src/com/android/messaging/datamodel/media/CustomVCardEntry.java48
-rw-r--r--src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java133
-rw-r--r--src/com/android/messaging/datamodel/media/DecodedImageResource.java254
-rw-r--r--src/com/android/messaging/datamodel/media/EncodedImageResource.java162
-rw-r--r--src/com/android/messaging/datamodel/media/FileImageRequest.java108
-rw-r--r--src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java68
-rw-r--r--src/com/android/messaging/datamodel/media/GifImageResource.java110
-rw-r--r--src/com/android/messaging/datamodel/media/ImageRequest.java258
-rw-r--r--src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java113
-rw-r--r--src/com/android/messaging/datamodel/media/ImageResource.java63
-rw-r--r--src/com/android/messaging/datamodel/media/MediaBytes.java42
-rw-r--r--src/com/android/messaging/datamodel/media/MediaCache.java113
-rw-r--r--src/com/android/messaging/datamodel/media/MediaCacheManager.java69
-rw-r--r--src/com/android/messaging/datamodel/media/MediaRequest.java70
-rw-r--r--src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java38
-rw-r--r--src/com/android/messaging/datamodel/media/MediaResourceManager.java325
-rw-r--r--src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java64
-rw-r--r--src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java38
-rw-r--r--src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java120
-rw-r--r--src/com/android/messaging/datamodel/media/PoolableImageCache.java419
-rw-r--r--src/com/android/messaging/datamodel/media/RefCountedMediaResource.java164
-rw-r--r--src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java117
-rw-r--r--src/com/android/messaging/datamodel/media/UriImageRequest.java58
-rw-r--r--src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java95
-rw-r--r--src/com/android/messaging/datamodel/media/VCardRequest.java328
-rw-r--r--src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java35
-rw-r--r--src/com/android/messaging/datamodel/media/VCardResource.java52
-rw-r--r--src/com/android/messaging/datamodel/media/VCardResourceEntry.java389
-rw-r--r--src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java78
-rw-r--r--src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java44
-rw-r--r--src/com/android/messaging/mmslib/Downloads.java807
-rw-r--r--src/com/android/messaging/mmslib/InvalidHeaderValueException.java41
-rw-r--r--src/com/android/messaging/mmslib/MmsException.java60
-rw-r--r--src/com/android/messaging/mmslib/SqliteWrapper.java88
-rw-r--r--src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java89
-rw-r--r--src/com/android/messaging/mmslib/pdu/Base64.java167
-rw-r--r--src/com/android/messaging/mmslib/pdu/CharacterSets.java452
-rw-r--r--src/com/android/messaging/mmslib/pdu/DeliveryInd.java138
-rw-r--r--src/com/android/messaging/mmslib/pdu/EncodedStringValue.java298
-rw-r--r--src/com/android/messaging/mmslib/pdu/GenericPdu.java113
-rw-r--r--src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java159
-rw-r--r--src/com/android/messaging/mmslib/pdu/NotificationInd.java285
-rw-r--r--src/com/android/messaging/mmslib/pdu/NotifyRespInd.java114
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduBody.java87
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduComposer.java1260
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduContentTypes.java110
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduHeaders.java743
-rwxr-xr-xsrc/com/android/messaging/mmslib/pdu/PduParser.java2044
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduPart.java389
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduPersister.java1683
-rw-r--r--src/com/android/messaging/mmslib/pdu/QuotedPrintable.java68
-rw-r--r--src/com/android/messaging/mmslib/pdu/ReadOrigInd.java153
-rw-r--r--src/com/android/messaging/mmslib/pdu/ReadRecInd.java144
-rw-r--r--src/com/android/messaging/mmslib/pdu/RetrieveConf.java305
-rw-r--r--src/com/android/messaging/mmslib/pdu/SendConf.java117
-rw-r--r--src/com/android/messaging/mmslib/pdu/SendReq.java346
-rw-r--r--src/com/android/messaging/mmslib/util/AbstractCache.java112
-rw-r--r--src/com/android/messaging/mmslib/util/DownloadDrmHelper.java111
-rw-r--r--src/com/android/messaging/mmslib/util/DrmConvertSession.java174
-rw-r--r--src/com/android/messaging/mmslib/util/PduCache.java262
-rw-r--r--src/com/android/messaging/mmslib/util/PduCacheEntry.java44
-rw-r--r--src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java43
-rw-r--r--src/com/android/messaging/receiver/AbortSmsReceiver.java41
-rw-r--r--src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java49
-rw-r--r--src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java32
-rw-r--r--src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java43
-rw-r--r--src/com/android/messaging/receiver/MmsWapPushReceiver.java58
-rw-r--r--src/com/android/messaging/receiver/NotificationReceiver.java57
-rw-r--r--src/com/android/messaging/receiver/SendStatusReceiver.java96
-rw-r--r--src/com/android/messaging/receiver/SmsDeliverReceiver.java31
-rw-r--r--src/com/android/messaging/receiver/SmsReceiver.java375
-rw-r--r--src/com/android/messaging/receiver/StorageStatusReceiver.java38
-rw-r--r--src/com/android/messaging/sms/ApnDatabase.java374
-rw-r--r--src/com/android/messaging/sms/ApnsXmlProcessor.java329
-rw-r--r--src/com/android/messaging/sms/BugleApnSettingsLoader.java646
-rw-r--r--src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java201
-rw-r--r--src/com/android/messaging/sms/BugleUserAgentInfoLoader.java96
-rw-r--r--src/com/android/messaging/sms/DatabaseMessages.java1006
-rwxr-xr-xsrc/com/android/messaging/sms/MmsConfig.java309
-rw-r--r--src/com/android/messaging/sms/MmsFailureException.java102
-rw-r--r--src/com/android/messaging/sms/MmsSender.java312
-rw-r--r--src/com/android/messaging/sms/MmsSmsUtils.java204
-rw-r--r--src/com/android/messaging/sms/MmsUtils.java2747
-rw-r--r--src/com/android/messaging/sms/SmsException.java59
-rw-r--r--src/com/android/messaging/sms/SmsReleaseStorage.java166
-rw-r--r--src/com/android/messaging/sms/SmsSender.java315
-rw-r--r--src/com/android/messaging/sms/SmsStorageStatusManager.java102
-rw-r--r--src/com/android/messaging/sms/SystemProperties.java54
-rw-r--r--src/com/android/messaging/ui/AsyncImageView.java457
-rw-r--r--src/com/android/messaging/ui/AttachmentPreview.java331
-rw-r--r--src/com/android/messaging/ui/AttachmentPreviewFactory.java299
-rw-r--r--src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java59
-rw-r--r--src/com/android/messaging/ui/AudioAttachmentView.java329
-rw-r--r--src/com/android/messaging/ui/AudioPlaybackProgressBar.java121
-rw-r--r--src/com/android/messaging/ui/BaseBugleActivity.java51
-rw-r--r--src/com/android/messaging/ui/BaseBugleFragmentActivity.java43
-rw-r--r--src/com/android/messaging/ui/BasePagerViewHolder.java106
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantListItemView.java64
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantsActivity.java57
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantsFragment.java102
-rw-r--r--src/com/android/messaging/ui/BugleActionBarActivity.java356
-rw-r--r--src/com/android/messaging/ui/BugleAnimationTags.java38
-rw-r--r--src/com/android/messaging/ui/ClassZeroActivity.java205
-rw-r--r--src/com/android/messaging/ui/CompositeAdapter.java288
-rw-r--r--src/com/android/messaging/ui/ContactIconView.java152
-rw-r--r--src/com/android/messaging/ui/ConversationDrawables.java177
-rw-r--r--src/com/android/messaging/ui/CursorRecyclerAdapter.java333
-rw-r--r--src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java136
-rw-r--r--src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java26
-rw-r--r--src/com/android/messaging/ui/CustomHeaderViewPager.java91
-rw-r--r--src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java33
-rw-r--r--src/com/android/messaging/ui/FixedViewPagerAdapter.java132
-rw-r--r--src/com/android/messaging/ui/ImeDetectFrameLayout.java46
-rw-r--r--src/com/android/messaging/ui/LicenseActivity.java35
-rw-r--r--src/com/android/messaging/ui/LineWrapLayout.java232
-rw-r--r--src/com/android/messaging/ui/ListEmptyView.java72
-rw-r--r--src/com/android/messaging/ui/MaxHeightScrollView.java50
-rw-r--r--src/com/android/messaging/ui/MultiAttachmentLayout.java424
-rw-r--r--src/com/android/messaging/ui/OrientedBitmapDrawable.java104
-rw-r--r--src/com/android/messaging/ui/PagerViewHolder.java34
-rw-r--r--src/com/android/messaging/ui/PagingAwareViewPager.java95
-rw-r--r--src/com/android/messaging/ui/PermissionCheckActivity.java141
-rw-r--r--src/com/android/messaging/ui/PersistentInstanceState.java39
-rw-r--r--src/com/android/messaging/ui/PersonItemView.java242
-rw-r--r--src/com/android/messaging/ui/PlaceholderInsetDrawable.java72
-rw-r--r--src/com/android/messaging/ui/PlainTextEditText.java85
-rw-r--r--src/com/android/messaging/ui/PlaybackStateView.java43
-rw-r--r--src/com/android/messaging/ui/RemoteInputEntrypointActivity.java58
-rw-r--r--src/com/android/messaging/ui/SmsStorageLowWarningActivity.java36
-rw-r--r--src/com/android/messaging/ui/SmsStorageLowWarningFragment.java267
-rw-r--r--src/com/android/messaging/ui/SnackBar.java314
-rw-r--r--src/com/android/messaging/ui/SnackBarInteraction.java67
-rw-r--r--src/com/android/messaging/ui/SnackBarManager.java365
-rw-r--r--src/com/android/messaging/ui/TestActivity.java88
-rw-r--r--src/com/android/messaging/ui/UIIntents.java378
-rw-r--r--src/com/android/messaging/ui/UIIntentsImpl.java577
-rw-r--r--src/com/android/messaging/ui/VCardDetailActivity.java60
-rw-r--r--src/com/android/messaging/ui/VCardDetailAdapter.java120
-rw-r--r--src/com/android/messaging/ui/VCardDetailFragment.java197
-rw-r--r--src/com/android/messaging/ui/VideoThumbnailView.java343
-rw-r--r--src/com/android/messaging/ui/ViewPagerTabStrip.java102
-rw-r--r--src/com/android/messaging/ui/ViewPagerTabs.java236
-rw-r--r--src/com/android/messaging/ui/WidgetPickConversationActivity.java114
-rw-r--r--src/com/android/messaging/ui/animation/PopupTransitionAnimation.java302
-rw-r--r--src/com/android/messaging/ui/animation/RectEvaluatorCompat.java45
-rw-r--r--src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java208
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnEditorActivity.java463
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnPreference.java151
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java406
-rw-r--r--src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java262
-rw-r--r--src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java92
-rw-r--r--src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java246
-rw-r--r--src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java116
-rw-r--r--src/com/android/messaging/ui/appsettings/SettingsActivity.java178
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java56
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java183
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java119
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java172
-rw-r--r--src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java85
-rw-r--r--src/com/android/messaging/ui/contact/AllContactsListViewHolder.java62
-rw-r--r--src/com/android/messaging/ui/contact/ContactDropdownLayouter.java138
-rw-r--r--src/com/android/messaging/ui/contact/ContactListAdapter.java86
-rw-r--r--src/com/android/messaging/ui/contact/ContactListItemView.java177
-rw-r--r--src/com/android/messaging/ui/contact/ContactPickerFragment.java607
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientAdapter.java286
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java289
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java96
-rw-r--r--src/com/android/messaging/ui/contact/ContactSectionIndexer.java169
-rw-r--r--src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java63
-rw-r--r--src/com/android/messaging/ui/conversation/ComposeMessageView.java962
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationActivity.java379
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationActivityUiState.java306
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationFastScroller.java489
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationFragment.java1662
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationInput.java103
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationInputManager.java550
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java117
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java132
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageView.java1206
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationSimSelector.java128
-rw-r--r--src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java92
-rw-r--r--src/com/android/messaging/ui/conversation/LaunchConversationActivity.java134
-rw-r--r--src/com/android/messaging/ui/conversation/MessageBubbleBackground.java47
-rw-r--r--src/com/android/messaging/ui/conversation/MessageDetailsDialog.java381
-rw-r--r--src/com/android/messaging/ui/conversation/SimIconView.java51
-rw-r--r--src/com/android/messaging/ui/conversation/SimSelectorItemView.java90
-rw-r--r--src/com/android/messaging/ui/conversation/SimSelectorView.java169
-rw-r--r--src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java339
-rw-r--r--src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java96
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListActivity.java144
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java77
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListFragment.java446
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListItemView.java643
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java462
-rw-r--r--src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java81
-rw-r--r--src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java219
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java177
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java138
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java163
-rw-r--r--src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java66
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java66
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java329
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java99
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java34
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java147
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java134
-rw-r--r--src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java171
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioLevelSource.java73
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java130
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioRecordView.java351
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraManager.java1200
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java481
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java102
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraPreview.java152
-rw-r--r--src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java128
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java62
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java159
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridView.java315
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java230
-rw-r--r--src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java118
-rw-r--r--src/com/android/messaging/ui/mediapicker/ImagePersistTask.java172
-rw-r--r--src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java223
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaChooser.java216
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPicker.java736
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java44
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java563
-rw-r--r--src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java127
-rw-r--r--src/com/android/messaging/ui/mediapicker/PausableChronometer.java75
-rw-r--r--src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java114
-rw-r--r--src/com/android/messaging/ui/mediapicker/SoundLevels.java212
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java24
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java589
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java95
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java202
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java825
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/README.txt3
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java178
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java192
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java38
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java31
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java179
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java89
-rw-r--r--src/com/android/messaging/util/AccessibilityUtil.java170
-rw-r--r--src/com/android/messaging/util/Assert.java214
-rw-r--r--src/com/android/messaging/util/AvatarUriUtil.java320
-rw-r--r--src/com/android/messaging/util/BugleActivityUtil.java88
-rw-r--r--src/com/android/messaging/util/BugleApplicationPrefs.java45
-rw-r--r--src/com/android/messaging/util/BugleGservices.java72
-rw-r--r--src/com/android/messaging/util/BugleGservicesImpl.java68
-rw-r--r--src/com/android/messaging/util/BugleGservicesKeys.java298
-rw-r--r--src/com/android/messaging/util/BuglePrefs.java140
-rw-r--r--src/com/android/messaging/util/BuglePrefsImpl.java135
-rw-r--r--src/com/android/messaging/util/BuglePrefsKeys.java71
-rw-r--r--src/com/android/messaging/util/BugleSubscriptionPrefs.java95
-rw-r--r--src/com/android/messaging/util/BugleWidgetPrefs.java41
-rw-r--r--src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java157
-rw-r--r--src/com/android/messaging/util/CircularArray.java111
-rw-r--r--src/com/android/messaging/util/ConnectivityUtil.java247
-rw-r--r--src/com/android/messaging/util/ContactRecipientEntryUtils.java115
-rw-r--r--src/com/android/messaging/util/ContactUtil.java525
-rw-r--r--src/com/android/messaging/util/ContentType.java185
-rw-r--r--src/com/android/messaging/util/ConversationIdSet.java69
-rw-r--r--src/com/android/messaging/util/CubicBezierInterpolator.java120
-rw-r--r--src/com/android/messaging/util/Dates.java280
-rw-r--r--src/com/android/messaging/util/DebugUtils.java425
-rw-r--r--src/com/android/messaging/util/EmailAddress.java272
-rw-r--r--src/com/android/messaging/util/FallbackStrategies.java91
-rw-r--r--src/com/android/messaging/util/FileUtil.java140
-rw-r--r--src/com/android/messaging/util/GifTranscoder.java94
-rw-r--r--src/com/android/messaging/util/ImageUtils.java908
-rw-r--r--src/com/android/messaging/util/ImeUtil.java88
-rw-r--r--src/com/android/messaging/util/LogSaver.java293
-rw-r--r--src/com/android/messaging/util/LogUtil.java274
-rw-r--r--src/com/android/messaging/util/LoggingTimer.java70
-rw-r--r--src/com/android/messaging/util/LongSparseSet.java59
-rw-r--r--src/com/android/messaging/util/MaterialPalette.java27
-rw-r--r--src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java81
-rw-r--r--src/com/android/messaging/util/MediaUtil.java36
-rw-r--r--src/com/android/messaging/util/MediaUtilImpl.java66
-rw-r--r--src/com/android/messaging/util/NotificationPlayer.java363
-rw-r--r--src/com/android/messaging/util/OsUtil.java269
-rw-r--r--src/com/android/messaging/util/PendingIntentConstants.java40
-rw-r--r--src/com/android/messaging/util/PhoneUtils.java1011
-rw-r--r--src/com/android/messaging/util/RingtoneUtil.java53
-rw-r--r--src/com/android/messaging/util/SafeAsyncTask.java176
-rw-r--r--src/com/android/messaging/util/SwitchCompatUtils.java129
-rw-r--r--src/com/android/messaging/util/TextUtil.java73
-rw-r--r--src/com/android/messaging/util/ThreadUtil.java28
-rw-r--r--src/com/android/messaging/util/TintDrawableWrapper.java70
-rw-r--r--src/com/android/messaging/util/Trace.java115
-rw-r--r--src/com/android/messaging/util/Typefaces.java45
-rw-r--r--src/com/android/messaging/util/UiUtils.java438
-rw-r--r--src/com/android/messaging/util/UriUtil.java393
-rw-r--r--src/com/android/messaging/util/VersionUtil.java67
-rw-r--r--src/com/android/messaging/util/WakeLockHelper.java121
-rw-r--r--src/com/android/messaging/util/YouTubeUtil.java98
-rw-r--r--src/com/android/messaging/util/exif/ByteBufferInputStream.java48
-rw-r--r--src/com/android/messaging/util/exif/CountedDataInputStream.java140
-rw-r--r--src/com/android/messaging/util/exif/ExifData.java349
-rw-r--r--src/com/android/messaging/util/exif/ExifInterface.java2448
-rw-r--r--src/com/android/messaging/util/exif/ExifInvalidFormatException.java23
-rw-r--r--src/com/android/messaging/util/exif/ExifModifier.java196
-rw-r--r--src/com/android/messaging/util/exif/ExifOutputStream.java522
-rw-r--r--src/com/android/messaging/util/exif/ExifParser.java918
-rw-r--r--src/com/android/messaging/util/exif/ExifReader.java93
-rw-r--r--src/com/android/messaging/util/exif/ExifTag.java1008
-rw-r--r--src/com/android/messaging/util/exif/IfdData.java152
-rw-r--r--src/com/android/messaging/util/exif/IfdId.java31
-rw-r--r--src/com/android/messaging/util/exif/JpegHeader.java39
-rw-r--r--src/com/android/messaging/util/exif/OrderedDataOutputStream.java56
-rw-r--r--src/com/android/messaging/util/exif/Rational.java88
-rw-r--r--src/com/android/messaging/widget/BaseWidgetFactory.java232
-rw-r--r--src/com/android/messaging/widget/BaseWidgetProvider.java184
-rw-r--r--src/com/android/messaging/widget/BugleWidgetProvider.java114
-rw-r--r--src/com/android/messaging/widget/WidgetConversationListService.java281
-rw-r--r--src/com/android/messaging/widget/WidgetConversationProvider.java316
-rw-r--r--src/com/android/messaging/widget/WidgetConversationService.java521
425 files changed, 0 insertions, 102274 deletions
diff --git a/src/com/android/messaging/BugleApplication.java b/src/com/android/messaging/BugleApplication.java
deleted file mode 100644
index a5aea9f..0000000
--- a/src/com/android/messaging/BugleApplication.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging;
-
-import android.app.Application;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.v7.mms.CarrierConfigValuesLoader;
-import android.support.v7.mms.MmsManager;
-import android.telephony.CarrierConfigManager;
-
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.receiver.SmsReceiver;
-import com.android.messaging.sms.ApnDatabase;
-import com.android.messaging.sms.BugleApnSettingsLoader;
-import com.android.messaging.sms.BugleUserAgentInfoLoader;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.BuglePrefsKeys;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.Trace;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.lang.Thread.UncaughtExceptionHandler;
-
-/**
- * The application object
- */
-public class BugleApplication extends Application implements UncaughtExceptionHandler {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private UncaughtExceptionHandler sSystemUncaughtExceptionHandler;
- private static boolean sRunningTests = false;
-
- @VisibleForTesting
- protected static void setTestsRunning() {
- sRunningTests = true;
- }
-
- /**
- * @return true if we're running unit tests.
- */
- public static boolean isRunningTests() {
- return sRunningTests;
- }
-
- @Override
- public void onCreate() {
- Trace.beginSection("app.onCreate");
- super.onCreate();
-
- // Note onCreate is called in both test and real application environments
- if (!sRunningTests) {
- // Only create the factory if not running tests
- FactoryImpl.register(getApplicationContext(), this);
- } else {
- LogUtil.e(TAG, "BugleApplication.onCreate: FactoryImpl.register skipped for test run");
- }
-
- sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
- Thread.setDefaultUncaughtExceptionHandler(this);
- Trace.endSection();
- }
-
- @Override
- public void onConfigurationChanged(final Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- // Update conversation drawables when changing writing systems
- // (Right-To-Left / Left-To-Right)
- ConversationDrawables.get().updateDrawables();
- }
-
- // Called by the "real" factory from FactoryImpl.register() (i.e. not run in tests)
- public void initializeSync(final Factory factory) {
- Trace.beginSection("app.initializeSync");
- final Context context = factory.getApplicationContext();
- final BugleGservices bugleGservices = factory.getBugleGservices();
- final BuglePrefs buglePrefs = factory.getApplicationPrefs();
- final DataModel dataModel = factory.getDataModel();
- final CarrierConfigValuesLoader carrierConfigValuesLoader =
- factory.getCarrierConfigValuesLoader();
-
- maybeStartProfiling();
-
- BugleApplication.updateAppConfig(context);
-
- // Initialize MMS lib
- initMmsLib(context, bugleGservices, carrierConfigValuesLoader);
- // Initialize APN database
- ApnDatabase.initializeAppContext(context);
- // Fixup messages in flight if we crashed and send any pending
- dataModel.onApplicationCreated();
- // Register carrier config change receiver
- if (OsUtil.isAtLeastM()) {
- registerCarrierConfigChangeReceiver(context);
- }
-
- Trace.endSection();
- }
-
- private static void registerCarrierConfigChangeReceiver(final Context context) {
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- LogUtil.i(TAG, "Carrier config changed. Reloading MMS config.");
- MmsConfig.loadAsync();
- }
- }, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
- }
-
- private static void initMmsLib(final Context context, final BugleGservices bugleGservices,
- final CarrierConfigValuesLoader carrierConfigValuesLoader) {
- MmsManager.setApnSettingsLoader(new BugleApnSettingsLoader(context));
- MmsManager.setCarrierConfigValuesLoader(carrierConfigValuesLoader);
- MmsManager.setUserAgentInfoLoader(new BugleUserAgentInfoLoader(context));
- MmsManager.setUseWakeLock(true);
- // If Gservices is configured not to use mms api, force MmsManager to always use
- // legacy mms sending logic
- MmsManager.setForceLegacyMms(!bugleGservices.getBoolean(
- BugleGservicesKeys.USE_MMS_API_IF_PRESENT,
- BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT));
- bugleGservices.registerForChanges(new Runnable() {
- @Override
- public void run() {
- MmsManager.setForceLegacyMms(!bugleGservices.getBoolean(
- BugleGservicesKeys.USE_MMS_API_IF_PRESENT,
- BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT));
- }
- });
- }
-
- public static void updateAppConfig(final Context context) {
- // Make sure we set the correct state for the SMS/MMS receivers
- SmsReceiver.updateSmsReceiveHandler(context);
- }
-
- // Called from thread started in FactoryImpl.register() (i.e. not run in tests)
- public void initializeAsync(final Factory factory) {
- // Handle shared prefs upgrade & Load MMS Configuration
- Trace.beginSection("app.initializeAsync");
- maybeHandleSharedPrefsUpgrade(factory);
- MmsConfig.load();
- Trace.endSection();
- }
-
- @Override
- public void onLowMemory() {
- super.onLowMemory();
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "BugleApplication.onLowMemory");
- }
- Factory.get().reclaimMemory();
- }
-
- @Override
- public void uncaughtException(final Thread thread, final Throwable ex) {
- final boolean background = getMainLooper().getThread() != thread;
- if (background) {
- LogUtil.e(TAG, "Uncaught exception in background thread " + thread, ex);
-
- final Handler handler = new Handler(getMainLooper());
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- sSystemUncaughtExceptionHandler.uncaughtException(thread, ex);
- }
- });
- } else {
- sSystemUncaughtExceptionHandler.uncaughtException(thread, ex);
- }
- }
-
- private void maybeStartProfiling() {
- // App startup profiling support. To use it:
- // adb shell setprop log.tag.BugleProfile DEBUG
- // # Start the app, wait for a 30s, download trace file:
- // adb pull /data/data/com.android.messaging/cache/startup.trace /tmp
- // # Open trace file (using adt/tools/traceview)
- if (android.util.Log.isLoggable(LogUtil.PROFILE_TAG, android.util.Log.DEBUG)) {
- // Start method tracing with a big enough buffer and let it run for 30s.
- // Note we use a logging tag as we don't want to wait for gservices to start up.
- final File file = DebugUtils.getDebugFile("startup.trace", true);
- if (file != null) {
- android.os.Debug.startMethodTracing(file.getAbsolutePath(), 160 * 1024 * 1024);
- new Handler(Looper.getMainLooper()).postDelayed(
- new Runnable() {
- @Override
- public void run() {
- android.os.Debug.stopMethodTracing();
- // Allow world to see trace file
- DebugUtils.ensureReadable(file);
- LogUtil.d(LogUtil.PROFILE_TAG, "Tracing complete - "
- + file.getAbsolutePath());
- }
- }, 30000);
- }
- }
- }
-
- private void maybeHandleSharedPrefsUpgrade(final Factory factory) {
- final int existingVersion = factory.getApplicationPrefs().getInt(
- BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
- BuglePrefsKeys.SHARED_PREFERENCES_VERSION_DEFAULT);
- final int targetVersion = Integer.parseInt(getString(R.string.pref_version));
- if (targetVersion > existingVersion) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Upgrading shared prefs from " + existingVersion +
- " to " + targetVersion);
- try {
- // Perform upgrade on application-wide prefs.
- factory.getApplicationPrefs().onUpgrade(existingVersion, targetVersion);
- // Perform upgrade on each subscription's prefs.
- PhoneUtils.forEachActiveSubscription(new PhoneUtils.SubscriptionRunnable() {
- @Override
- public void runForSubscription(final int subId) {
- factory.getSubscriptionPrefs(subId)
- .onUpgrade(existingVersion, targetVersion);
- }
- });
- factory.getApplicationPrefs().putInt(BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
- targetVersion);
- } catch (final Exception ex) {
- // Upgrade failed. Don't crash the app because we can always fall back to the
- // default settings.
- LogUtil.e(LogUtil.BUGLE_TAG, "Failed to upgrade shared prefs", ex);
- }
- } else if (targetVersion < existingVersion) {
- // We don't care about downgrade since real user shouldn't encounter this, so log it
- // and ignore any prefs migration.
- LogUtil.e(LogUtil.BUGLE_TAG, "Shared prefs downgrade requested and ignored. " +
- "oldVersion = " + existingVersion + ", newVersion = " + targetVersion);
- }
- }
-}
diff --git a/src/com/android/messaging/Factory.java b/src/com/android/messaging/Factory.java
deleted file mode 100644
index c542a54..0000000
--- a/src/com/android/messaging/Factory.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging;
-
-import android.content.Context;
-
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.MemoryCacheManager;
-import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver;
-import com.android.messaging.datamodel.media.MediaCacheManager;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.sms.BugleCarrierConfigValuesLoader;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.MediaUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-public abstract class Factory {
-
- // Making this volatile because on the unit tests, setInstance is called from a unit test
- // thread, and then it's read on the UI thread.
- private static volatile Factory sInstance;
- @VisibleForTesting
- protected static boolean sRegistered;
- @VisibleForTesting
- protected static boolean sInitialized;
-
- public static Factory get() {
- return sInstance;
- }
-
- protected static void setInstance(final Factory factory) {
- // Not allowed to call this after real application initialization is complete
- Assert.isTrue(!sRegistered);
- Assert.isTrue(!sInitialized);
- sInstance = factory;
- }
- public abstract void onRequiredPermissionsAcquired();
-
- public abstract Context getApplicationContext();
- public abstract DataModel getDataModel();
- public abstract BugleGservices getBugleGservices();
- public abstract BuglePrefs getApplicationPrefs();
- public abstract BuglePrefs getSubscriptionPrefs(int subId);
- public abstract BuglePrefs getWidgetPrefs();
- public abstract UIIntents getUIIntents();
- public abstract MemoryCacheManager getMemoryCacheManager();
- public abstract MediaResourceManager getMediaResourceManager();
- public abstract MediaCacheManager getMediaCacheManager();
- public abstract ContactContentObserver getContactContentObserver();
- public abstract PhoneUtils getPhoneUtils(int subId);
- public abstract MediaUtil getMediaUtil();
- public abstract BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader();
- // Note this needs to run from any thread
- public abstract void reclaimMemory();
-
- public abstract void onActivityResume();
-}
diff --git a/src/com/android/messaging/FactoryImpl.java b/src/com/android/messaging/FactoryImpl.java
deleted file mode 100644
index a862308..0000000
--- a/src/com/android/messaging/FactoryImpl.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging;
-
-import android.content.Context;
-import android.os.Process;
-import android.telephony.SmsManager;
-import android.util.SparseArray;
-
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DataModelImpl;
-import com.android.messaging.datamodel.MemoryCacheManager;
-import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.BugleMediaCacheManager;
-import com.android.messaging.datamodel.media.MediaCacheManager;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.sms.BugleCarrierConfigValuesLoader;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.UIIntentsImpl;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleApplicationPrefs;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesImpl;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.BugleSubscriptionPrefs;
-import com.android.messaging.util.BugleWidgetPrefs;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaUtil;
-import com.android.messaging.util.MediaUtilImpl;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-class FactoryImpl extends Factory {
- private BugleApplication mApplication;
- private DataModel mDataModel;
- private BugleGservices mBugleGservices;
- private BugleApplicationPrefs mBugleApplicationPrefs;
- private BugleWidgetPrefs mBugleWidgetPrefs;
- private Context mApplicationContext;
- private UIIntents mUIIntents;
- private MemoryCacheManager mMemoryCacheManager;
- private MediaResourceManager mMediaResourceManager;
- private MediaCacheManager mMediaCacheManager;
- private ContactContentObserver mContactContentObserver;
- private PhoneUtils mPhoneUtils;
- private MediaUtil mMediaUtil;
- private SparseArray<BugleSubscriptionPrefs> mSubscriptionPrefs;
- private BugleCarrierConfigValuesLoader mCarrierConfigValuesLoader;
-
- // Cached instance for Pre-L_MR1
- private static final Object PHONEUTILS_INSTANCE_LOCK = new Object();
- private static PhoneUtils sPhoneUtilsInstancePreLMR1 = null;
- // Cached subId->instance for L_MR1 and beyond
- private static final ConcurrentHashMap<Integer, PhoneUtils> sPhoneUtilsInstanceCacheLMR1 =
- new ConcurrentHashMap<>();
-
- private FactoryImpl() {
- }
-
- public static Factory register(final Context applicationContext,
- final BugleApplication application) {
- // This only gets called once (from BugleApplication.onCreate), but its not called in tests.
- Assert.isTrue(!sRegistered);
- Assert.isNull(Factory.get());
-
- final FactoryImpl factory = new FactoryImpl();
- Factory.setInstance(factory);
- sRegistered = true;
-
- // At this point Factory is published. Services can now get initialized and depend on
- // Factory.get().
- factory.mApplication = application;
- factory.mApplicationContext = applicationContext;
- factory.mMemoryCacheManager = new MemoryCacheManager();
- factory.mMediaCacheManager = new BugleMediaCacheManager();
- factory.mMediaResourceManager = new MediaResourceManager();
- factory.mBugleGservices = new BugleGservicesImpl(applicationContext);
- factory.mBugleApplicationPrefs = new BugleApplicationPrefs(applicationContext);
- factory.mDataModel = new DataModelImpl(applicationContext);
- factory.mBugleWidgetPrefs = new BugleWidgetPrefs(applicationContext);
- factory.mUIIntents = new UIIntentsImpl();
- factory.mContactContentObserver = new ContactContentObserver();
- factory.mMediaUtil = new MediaUtilImpl();
- factory.mSubscriptionPrefs = new SparseArray<BugleSubscriptionPrefs>();
- factory.mCarrierConfigValuesLoader = new BugleCarrierConfigValuesLoader(applicationContext);
-
- Assert.initializeGservices(factory.mBugleGservices);
- LogUtil.initializeGservices(factory.mBugleGservices);
-
- if (OsUtil.hasRequiredPermissions()) {
- factory.onRequiredPermissionsAcquired();
- }
-
- return factory;
- }
-
- @Override
- public void onRequiredPermissionsAcquired() {
- if (sInitialized) {
- return;
- }
- sInitialized = true;
-
- mApplication.initializeSync(this);
-
- final Thread asyncInitialization = new Thread() {
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- mApplication.initializeAsync(FactoryImpl.this);
- }
- };
- asyncInitialization.start();
- }
-
- @Override
- public Context getApplicationContext() {
- return mApplicationContext;
- }
-
- @Override
- public DataModel getDataModel() {
- return mDataModel;
- }
-
- @Override
- public BugleGservices getBugleGservices() {
- return mBugleGservices;
- }
-
- @Override
- public BuglePrefs getApplicationPrefs() {
- return mBugleApplicationPrefs;
- }
-
- @Override
- public BuglePrefs getWidgetPrefs() {
- return mBugleWidgetPrefs;
- }
-
- @Override
- public BuglePrefs getSubscriptionPrefs(int subId) {
- subId = PhoneUtils.getDefault().getEffectiveSubId(subId);
- BugleSubscriptionPrefs pref = mSubscriptionPrefs.get(subId);
- if (pref == null) {
- synchronized (this) {
- if ((pref = mSubscriptionPrefs.get(subId)) == null) {
- pref = new BugleSubscriptionPrefs(getApplicationContext(), subId);
- mSubscriptionPrefs.put(subId, pref);
- }
- }
- }
- return pref;
- }
-
- @Override
- public UIIntents getUIIntents() {
- return mUIIntents;
- }
-
- @Override
- public MemoryCacheManager getMemoryCacheManager() {
- return mMemoryCacheManager;
- }
-
- @Override
- public MediaResourceManager getMediaResourceManager() {
- return mMediaResourceManager;
- }
-
- @Override
- public MediaCacheManager getMediaCacheManager() {
- return mMediaCacheManager;
- }
-
- @Override
- public ContactContentObserver getContactContentObserver() {
- return mContactContentObserver;
- }
-
- @Override
- public PhoneUtils getPhoneUtils(int subId) {
- if (OsUtil.isAtLeastL_MR1()) {
- if (subId == ParticipantData.DEFAULT_SELF_SUB_ID) {
- subId = SmsManager.getDefaultSmsSubscriptionId();
- }
- if (subId < 0) {
- LogUtil.w(LogUtil.BUGLE_TAG, "PhoneUtils.getForLMR1(): invalid subId = " + subId);
- subId = ParticipantData.DEFAULT_SELF_SUB_ID;
- }
- PhoneUtils instance = sPhoneUtilsInstanceCacheLMR1.get(subId);
- if (instance == null) {
- instance = new PhoneUtils.PhoneUtilsLMR1(subId);
- sPhoneUtilsInstanceCacheLMR1.putIfAbsent(subId, instance);
- }
- return instance;
- } else {
- Assert.isTrue(subId == ParticipantData.DEFAULT_SELF_SUB_ID);
- if (sPhoneUtilsInstancePreLMR1 == null) {
- synchronized (PHONEUTILS_INSTANCE_LOCK) {
- if (sPhoneUtilsInstancePreLMR1 == null) {
- sPhoneUtilsInstancePreLMR1 = new PhoneUtils.PhoneUtilsPreLMR1();
- }
- }
- }
- return sPhoneUtilsInstancePreLMR1;
- }
- }
-
- @Override
- public void reclaimMemory() {
- mMemoryCacheManager.reclaimMemory();
- }
-
- @Override
- public void onActivityResume() {
- }
-
- @Override
- public MediaUtil getMediaUtil() {
- return mMediaUtil;
- }
-
- @Override
- public BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader() {
- return mCarrierConfigValuesLoader;
- }
-}
diff --git a/src/com/android/messaging/annotation/VisibleForAnimation.java b/src/com/android/messaging/annotation/VisibleForAnimation.java
deleted file mode 100644
index f97d827..0000000
--- a/src/com/android/messaging/annotation/VisibleForAnimation.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.annotation;
-
-/**
- * An annotation for class members that are made visible for Android's ObjectAnimator to work
- * properly through reflection.
- */
-public @interface VisibleForAnimation {
-}
diff --git a/src/com/android/messaging/datamodel/BitmapPool.java b/src/com/android/messaging/datamodel/BitmapPool.java
deleted file mode 100644
index 1ec4f76..0000000
--- a/src/com/android/messaging/datamodel/BitmapPool.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.SparseArray;
-
-import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.io.InputStream;
-
-/**
- * Class for creating / loading / reusing bitmaps. This class allow the user to create a new bitmap,
- * reuse an bitmap from the pool and to return a bitmap for future reuse. The pool of bitmaps
- * allows for faster decode and more efficient memory usage.
- * Note: consumers should not create BitmapPool directly, but instead get the pool they want from
- * the BitmapPoolManager.
- */
-public class BitmapPool implements MemoryCache {
- public static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF;
-
- protected static final boolean VERBOSE = false;
-
- /**
- * Number of reuse failures to skip before reporting.
- */
- private static final int FAILED_REPORTING_FREQUENCY = 100;
-
- /**
- * Count of reuse failures which have occurred.
- */
- private static volatile int sFailedBitmapReuseCount = 0;
-
- /**
- * Overall pool data structure which currently only supports rectangular bitmaps. The size of
- * one of the sides is used to index into the SparseArray.
- */
- private final SparseArray<SingleSizePool> mPool;
- private final Object mPoolLock = new Object();
- private final String mPoolName;
- private final int mMaxSize;
-
- /**
- * Inner structure which holds a pool of bitmaps all the same size (i.e. all have the same
- * width as each other and height as each other, but not necessarily the same).
- */
- private class SingleSizePool {
- int mNumItems;
- final Bitmap[] mBitmaps;
-
- SingleSizePool(final int maxPoolSize) {
- mNumItems = 0;
- mBitmaps = new Bitmap[maxPoolSize];
- }
- }
-
- /**
- * Creates a pool of reused bitmaps with helper decode methods which will attempt to use the
- * reclaimed bitmaps. This will help speed up the creation of bitmaps by using already allocated
- * bitmaps.
- * @param maxSize The overall max size of the pool. When the pool exceeds this size, all calls
- * to reclaimBitmap(Bitmap) will result in recycling the bitmap.
- * @param name Name of the bitmap pool and only used for logging. Can not be null.
- */
- BitmapPool(final int maxSize, @NonNull final String name) {
- Assert.isTrue(maxSize > 0);
- Assert.isTrue(!TextUtils.isEmpty(name));
- mPoolName = name;
- mMaxSize = maxSize;
- mPool = new SparseArray<SingleSizePool>();
- }
-
- @Override
- public void reclaim() {
- synchronized (mPoolLock) {
- for (int p = 0; p < mPool.size(); p++) {
- final SingleSizePool singleSizePool = mPool.valueAt(p);
- for (int i = 0; i < singleSizePool.mNumItems; i++) {
- singleSizePool.mBitmaps[i].recycle();
- singleSizePool.mBitmaps[i] = null;
- }
- singleSizePool.mNumItems = 0;
- }
- mPool.clear();
- }
- }
-
- /**
- * Creates a new BitmapFactory.Options.
- */
- public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled,
- final int inputDensity, final int targetDensity) {
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inScaled = scaled;
- options.inDensity = inputDensity;
- options.inTargetDensity = targetDensity;
- options.inSampleSize = 1;
- options.inJustDecodeBounds = false;
- options.inMutable = true;
- return options;
- }
-
- /**
- * @return The pool key for the provided image dimensions or 0 if either width or height is
- * greater than the max supported image dimension.
- */
- private int getPoolKey(final int width, final int height) {
- if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) {
- return 0;
- }
- return (width << 16) | height;
- }
-
- /**
- *
- * @return A bitmap in the pool with the specified dimensions or null if no bitmap with the
- * specified dimension is available.
- */
- private Bitmap findPoolBitmap(final int width, final int height) {
- final int poolKey = getPoolKey(width, height);
- if (poolKey != 0) {
- synchronized (mPoolLock) {
- // Take a bitmap from the pool if one is available
- final SingleSizePool singlePool = mPool.get(poolKey);
- if (singlePool != null && singlePool.mNumItems > 0) {
- singlePool.mNumItems--;
- final Bitmap foundBitmap = singlePool.mBitmaps[singlePool.mNumItems];
- singlePool.mBitmaps[singlePool.mNumItems] = null;
- return foundBitmap;
- }
- }
- }
- return null;
- }
-
- /**
- * Internal function to try and find a bitmap in the pool which matches the desired width and
- * height and then set that in the bitmap options properly.
- *
- * TODO: Why do we take a width/height? Shouldn't this already be in the
- * BitmapFactory.Options instance? Can we assert that they match?
- * @param optionsTmp The BitmapFactory.Options to update with the bitmap for the system to try
- * to reuse.
- * @param width The width of the reusable bitmap.
- * @param height The height of the reusable bitmap.
- */
- private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width,
- final int height) {
- if (optionsTmp.inJustDecodeBounds) {
- return;
- }
- optionsTmp.inBitmap = findPoolBitmap(width, height);
- }
-
- /**
- * Load a resource into a bitmap. Uses a bitmap from the pool if possible to reduce memory
- * turnover.
- * @param resourceId Resource id to load.
- * @param resources Application resources. Cannot be null.
- * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). Cannot
- * be null.
- * @param width The width of the bitmap.
- * @param height The height of the bitmap.
- * @return The decoded Bitmap with the resource drawn in it.
- */
- public Bitmap decodeSampledBitmapFromResource(final int resourceId,
- @NonNull final Resources resources, @NonNull final BitmapFactory.Options optionsTmp,
- final int width, final int height) {
- Assert.notNull(resources);
- Assert.notNull(optionsTmp);
- Assert.isTrue(width > 0);
- Assert.isTrue(height > 0);
- assignPoolBitmap(optionsTmp, width, height);
- Bitmap b = null;
- try {
- b = BitmapFactory.decodeResource(resources, resourceId, optionsTmp);
- } catch (final IllegalArgumentException e) {
- // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
- if (optionsTmp.inBitmap != null) {
- optionsTmp.inBitmap = null;
- b = BitmapFactory.decodeResource(resources, resourceId, optionsTmp);
- sFailedBitmapReuseCount++;
- if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
- LogUtil.w(LogUtil.BUGLE_TAG,
- "Pooled bitmap consistently not being reused count = " +
- sFailedBitmapReuseCount);
- }
- }
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Oom decoding resource " + resourceId);
- reclaim();
- }
- return b;
- }
-
- /**
- * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce memory
- * turnover.
- * @param inputStream InputStream load. Cannot be null.
- * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). Cannot
- * be null.
- * @param width The width of the bitmap.
- * @param height The height of the bitmap.
- * @return The decoded Bitmap with the resource drawn in it.
- */
- public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream,
- @NonNull final BitmapFactory.Options optionsTmp,
- final int width, final int height) {
- Assert.notNull(inputStream);
- Assert.isTrue(width > 0);
- Assert.isTrue(height > 0);
- assignPoolBitmap(optionsTmp, width, height);
- Bitmap b = null;
- try {
- b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
- } catch (final IllegalArgumentException e) {
- // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
- if (optionsTmp.inBitmap != null) {
- optionsTmp.inBitmap = null;
- b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
- sFailedBitmapReuseCount++;
- if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
- LogUtil.w(LogUtil.BUGLE_TAG,
- "Pooled bitmap consistently not being reused count = " +
- sFailedBitmapReuseCount);
- }
- }
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Oom decoding inputStream");
- reclaim();
- }
- return b;
- }
-
- /**
- * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce memory
- * turnover.
- * @param bytes Encoded bytes to draw on the bitmap. Cannot be null.
- * @param optionsTmp The bitmap will set here and the input should be generated from
- * getBitmapOptionsForPool(). Cannot be null.
- * @param width The width of the bitmap.
- * @param height The height of the bitmap.
- * @return A Bitmap with the encoded bytes drawn in it.
- */
- public Bitmap decodeByteArray(@NonNull final byte[] bytes,
- @NonNull final BitmapFactory.Options optionsTmp, final int width,
- final int height) throws OutOfMemoryError {
- Assert.notNull(bytes);
- Assert.notNull(optionsTmp);
- Assert.isTrue(width > 0);
- Assert.isTrue(height > 0);
- assignPoolBitmap(optionsTmp, width, height);
- Bitmap b = null;
- try {
- b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
- } catch (final IllegalArgumentException e) {
- if (VERBOSE) {
- LogUtil.v(LogUtil.BUGLE_TAG, "BitmapPool(" + mPoolName +
- ") Unable to use pool bitmap");
- }
- // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
- // (i.e. without the bitmap from the pool)
- if (optionsTmp.inBitmap != null) {
- optionsTmp.inBitmap = null;
- b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
- sFailedBitmapReuseCount++;
- if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
- LogUtil.w(LogUtil.BUGLE_TAG,
- "Pooled bitmap consistently not being reused count = " +
- sFailedBitmapReuseCount);
- }
- }
- }
- return b;
- }
-
- /**
- * Creates a bitmap with the given size, this will reuse a bitmap in the pool, if one is
- * available, otherwise this will create a new one.
- * @param width The desired width of the bitmap.
- * @param height The desired height of the bitmap.
- * @return A bitmap with the desired width and height, this maybe a reused bitmap from the pool.
- */
- public Bitmap createOrReuseBitmap(final int width, final int height) {
- Bitmap b = findPoolBitmap(width, height);
- if (b == null) {
- b = createBitmap(width, height);
- }
- return b;
- }
-
- /**
- * This will create a new bitmap regardless of pool state.
- * @param width The desired width of the bitmap.
- * @param height The desired height of the bitmap.
- * @return A bitmap with the desired width and height.
- */
- private Bitmap createBitmap(final int width, final int height) {
- return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- }
-
- /**
- * Called when a bitmap is finished being used so that it can be used for another bitmap in the
- * future or recycled. Any bitmaps returned should not be used by the caller again.
- * @param b The bitmap to return to the pool for future usage or recycled. This cannot be null.
- */
- public void reclaimBitmap(@NonNull final Bitmap b) {
- Assert.notNull(b);
- final int poolKey = getPoolKey(b.getWidth(), b.getHeight());
- if (poolKey == 0 || !b.isMutable()) {
- // Unsupported image dimensions or a immutable bitmap.
- b.recycle();
- return;
- }
- synchronized (mPoolLock) {
- SingleSizePool singleSizePool = mPool.get(poolKey);
- if (singleSizePool == null) {
- singleSizePool = new SingleSizePool(mMaxSize);
- mPool.append(poolKey, singleSizePool);
- }
- if (singleSizePool.mNumItems < singleSizePool.mBitmaps.length) {
- singleSizePool.mBitmaps[singleSizePool.mNumItems] = b;
- singleSizePool.mNumItems++;
- } else {
- b.recycle();
- }
- }
- }
-
- /**
- * @return whether the pool is full for a given width and height.
- */
- public boolean isFull(final int width, final int height) {
- final int poolKey = getPoolKey(width, height);
- synchronized (mPoolLock) {
- final SingleSizePool singleSizePool = mPool.get(poolKey);
- if (singleSizePool != null &&
- singleSizePool.mNumItems >= singleSizePool.mBitmaps.length) {
- return true;
- }
- return false;
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/BoundCursorLoader.java b/src/com/android/messaging/datamodel/BoundCursorLoader.java
deleted file mode 100644
index 84d38e6..0000000
--- a/src/com/android/messaging/datamodel/BoundCursorLoader.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.content.CursorLoader;
-import android.net.Uri;
-
-/**
- * Extension to basic cursor loader that has an attached binding id
- */
-public class BoundCursorLoader extends CursorLoader {
- private final String mBindingId;
-
- /**
- * Create cursor loader for associated binding id
- */
- public BoundCursorLoader(final String bindingId, final Context context, final Uri uri,
- final String[] projection, final String selection, final String[] selectionArgs,
- final String sortOrder) {
- super(context, uri, projection, selection, selectionArgs, sortOrder);
- mBindingId = bindingId;
- }
-
- /**
- * Binding id associated with this loader - consume can check to verify data still valid
- * @return
- */
- public String getBindingId() {
- return mBindingId;
- }
-}
diff --git a/src/com/android/messaging/datamodel/BugleDatabaseOperations.java b/src/com/android/messaging/datamodel/BugleDatabaseOperations.java
deleted file mode 100644
index 8c40177..0000000
--- a/src/com/android/messaging/datamodel/BugleDatabaseOperations.java
+++ /dev/null
@@ -1,1919 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDoneException;
-import android.database.sqlite.SQLiteStatement;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.support.v4.util.ArrayMap;
-import android.support.v4.util.SimpleArrayMap;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.ParticipantRefresh.ConversationParticipantsQuery;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.UriUtil;
-import com.android.messaging.widget.WidgetConversationProvider;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import javax.annotation.Nullable;
-
-
-/**
- * This class manages updating our local database
- */
-public class BugleDatabaseOperations {
-
- private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
-
- // Global cache of phone numbers -> participant id mapping since this call is expensive.
- private static final ArrayMap<String, String> sNormalizedPhoneNumberToParticipantIdCache =
- new ArrayMap<String, String>();
-
- /**
- * Convert list of recipient strings (email/phone number) into list of ConversationParticipants
- *
- * @param recipients The recipient list
- * @param refSubId The subId used to normalize phone numbers in the recipients
- */
- static ArrayList<ParticipantData> getConversationParticipantsFromRecipients(
- final List<String> recipients, final int refSubId) {
- // Generate a list of partially formed participants
- final ArrayList<ParticipantData> participants = new
- ArrayList<ParticipantData>();
-
- if (recipients != null) {
- for (final String recipient : recipients) {
- participants.add(ParticipantData.getFromRawPhoneBySimLocale(recipient, refSubId));
- }
- }
- return participants;
- }
-
- /**
- * Sanitize a given list of conversation participants by de-duping and stripping out self
- * phone number in group conversation.
- */
- @DoesNotRunOnMainThread
- public static void sanitizeConversationParticipants(final List<ParticipantData> participants) {
- Assert.isNotMainThread();
- if (participants.size() > 0) {
- // First remove redundant phone numbers
- final HashSet<String> recipients = new HashSet<String>();
- for (int i = participants.size() - 1; i >= 0; i--) {
- final String recipient = participants.get(i).getNormalizedDestination();
- if (!recipients.contains(recipient)) {
- recipients.add(recipient);
- } else {
- participants.remove(i);
- }
- }
- if (participants.size() > 1) {
- // Remove self phone number from group conversation.
- final HashSet<String> selfNumbers =
- PhoneUtils.getDefault().getNormalizedSelfNumbers();
- int removed = 0;
- // Do this two-pass scan to avoid unnecessary memory allocation.
- // Prescan to count the self numbers in the list
- for (final ParticipantData p : participants) {
- if (selfNumbers.contains(p.getNormalizedDestination())) {
- removed++;
- }
- }
- // If all are self numbers, maybe that's what the user wants, just leave
- // the participants as is. Otherwise, do another scan to remove self numbers.
- if (removed < participants.size()) {
- for (int i = participants.size() - 1; i >= 0; i--) {
- final String recipient = participants.get(i).getNormalizedDestination();
- if (selfNumbers.contains(recipient)) {
- participants.remove(i);
- }
- }
- }
- }
- }
- }
-
- /**
- * Convert list of ConversationParticipants into recipient strings (email/phone number)
- */
- @DoesNotRunOnMainThread
- public static ArrayList<String> getRecipientsFromConversationParticipants(
- final List<ParticipantData> participants) {
- Assert.isNotMainThread();
- // First find the thread id for this list of participants.
- final ArrayList<String> recipients = new ArrayList<String>();
-
- for (final ParticipantData participant : participants) {
- recipients.add(participant.getSendDestination());
- }
- return recipients;
- }
-
- /**
- * Get or create a conversation based on the message's thread id
- *
- * NOTE: There are phones on which you can't get the recipients from the thread id for SMS
- * until you have a message, so use getOrCreateConversationFromRecipient instead.
- *
- * TODO: Should this be in MMS/SMS code?
- *
- * @param db the database
- * @param threadId The message's thread
- * @param senderBlocked Flag whether sender of message is in blocked people list
- * @param refSubId The reference subId for canonicalize phone numbers
- * @return conversationId
- */
- @DoesNotRunOnMainThread
- public static String getOrCreateConversationFromThreadId(final DatabaseWrapper db,
- final long threadId, final boolean senderBlocked, final int refSubId) {
- Assert.isNotMainThread();
- final List<String> recipients = MmsUtils.getRecipientsByThread(threadId);
- final ArrayList<ParticipantData> participants =
- getConversationParticipantsFromRecipients(recipients, refSubId);
-
- return getOrCreateConversation(db, threadId, senderBlocked, participants, false, false,
- null);
- }
-
- /**
- * Get or create a conversation based on provided recipient
- *
- * @param db the database
- * @param threadId The message's thread
- * @param senderBlocked Flag whether sender of message is in blocked people list
- * @param recipient recipient for thread
- * @return conversationId
- */
- @DoesNotRunOnMainThread
- public static String getOrCreateConversationFromRecipient(final DatabaseWrapper db,
- final long threadId, final boolean senderBlocked, final ParticipantData recipient) {
- Assert.isNotMainThread();
- final ArrayList<ParticipantData> recipients = new ArrayList<>(1);
- recipients.add(recipient);
- return getOrCreateConversation(db, threadId, senderBlocked, recipients, false, false, null);
- }
-
- /**
- * Get or create a conversation based on provided participants
- *
- * @param db the database
- * @param threadId The message's thread
- * @param archived Flag whether the conversation should be created archived
- * @param participants list of conversation participants
- * @param noNotification If notification should be disabled
- * @param noVibrate If vibrate on notification should be disabled
- * @param soundUri If there is custom sound URI
- * @return a conversation id
- */
- @DoesNotRunOnMainThread
- public static String getOrCreateConversation(final DatabaseWrapper db, final long threadId,
- final boolean archived, final ArrayList<ParticipantData> participants,
- boolean noNotification, boolean noVibrate, String soundUri) {
- Assert.isNotMainThread();
-
- // Check to see if this conversation is already in out local db cache
- String conversationId = BugleDatabaseOperations.getExistingConversation(db, threadId,
- false);
-
- if (conversationId == null) {
- final String conversationName = ConversationListItemData.generateConversationName(
- participants);
-
- // Create the conversation with the default self participant which always maps to
- // the system default subscription.
- final ParticipantData self = ParticipantData.getSelfParticipant(
- ParticipantData.DEFAULT_SELF_SUB_ID);
-
- db.beginTransaction();
- try {
- // Look up the "self" participantId (creating if necessary)
- final String selfId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
- // Create a new conversation
- conversationId = BugleDatabaseOperations.createConversationInTransaction(
- db, threadId, conversationName, selfId, participants, archived,
- noNotification, noVibrate, soundUri);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- return conversationId;
- }
-
- /**
- * Get a conversation from the local DB based on the message's thread id.
- *
- * @param dbWrapper The database
- * @param threadId The message's thread in the SMS database
- * @param senderBlocked Flag whether sender of message is in blocked people list
- * @return The existing conversation id or null
- */
- @VisibleForTesting
- @DoesNotRunOnMainThread
- public static String getExistingConversation(final DatabaseWrapper dbWrapper,
- final long threadId, final boolean senderBlocked) {
- Assert.isNotMainThread();
- String conversationId = null;
-
- Cursor cursor = null;
- try {
- // Look for an existing conversation in the db with this thread id
- cursor = dbWrapper.rawQuery("SELECT " + ConversationColumns._ID
- + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
- + " WHERE " + ConversationColumns.SMS_THREAD_ID + "=" + threadId,
- null);
-
- if (cursor.moveToFirst()) {
- Assert.isTrue(cursor.getCount() == 1);
- conversationId = cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return conversationId;
- }
-
- /**
- * Get the thread id for an existing conversation from the local DB.
- *
- * @param dbWrapper The database
- * @param conversationId The conversation to look up thread for
- * @return The thread id. Returns -1 if the conversation was not found or if it was found
- * but the thread column was NULL.
- */
- @DoesNotRunOnMainThread
- public static long getThreadId(final DatabaseWrapper dbWrapper, final String conversationId) {
- Assert.isNotMainThread();
- long threadId = -1;
-
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
- new String[] { ConversationColumns.SMS_THREAD_ID },
- ConversationColumns._ID + " =?",
- new String[] { conversationId },
- null, null, null);
-
- if (cursor.moveToFirst()) {
- Assert.isTrue(cursor.getCount() == 1);
- if (!cursor.isNull(0)) {
- threadId = cursor.getLong(0);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return threadId;
- }
-
- @DoesNotRunOnMainThread
- public static boolean isBlockedDestination(final DatabaseWrapper db, final String destination) {
- Assert.isNotMainThread();
- return isBlockedParticipant(db, destination, ParticipantColumns.NORMALIZED_DESTINATION);
- }
-
- static boolean isBlockedParticipant(final DatabaseWrapper db, final String participantId) {
- return isBlockedParticipant(db, participantId, ParticipantColumns._ID);
- }
-
- static boolean isBlockedParticipant(final DatabaseWrapper db, final String value,
- final String column) {
- Cursor cursor = null;
- try {
- cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
- new String[] { ParticipantColumns.BLOCKED },
- column + "=? AND " + ParticipantColumns.SUB_ID + "=?",
- new String[] { value,
- Integer.toString(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
- null, null, null);
-
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- return cursor.getInt(0) == 1;
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return false; // if there's no row, it's not blocked :-)
- }
-
- /**
- * Create a conversation in the local DB based on the message's thread id.
- *
- * It's up to the caller to make sure that this is all inside a transaction. It will return
- * null if it's not in the local DB.
- *
- * @param dbWrapper The database
- * @param threadId The message's thread
- * @param selfId The selfId to make default for this conversation
- * @param archived Flag whether the conversation should be created archived
- * @param noNotification If notification should be disabled
- * @param noVibrate If vibrate on notification should be disabled
- * @param soundUri The customized sound
- * @return The existing conversation id or new conversation id
- */
- static String createConversationInTransaction(final DatabaseWrapper dbWrapper,
- final long threadId, final String conversationName, final String selfId,
- final List<ParticipantData> participants, final boolean archived,
- boolean noNotification, boolean noVibrate, String soundUri) {
- // We want conversation and participant creation to be atomic
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- boolean hasEmailAddress = false;
- for (final ParticipantData participant : participants) {
- Assert.isTrue(!participant.isSelf());
- if (participant.isEmail()) {
- hasEmailAddress = true;
- }
- }
-
- // TODO : Conversations state - normal vs. archived
-
- // Insert a new local conversation for this thread id
- final ContentValues values = new ContentValues();
- values.put(ConversationColumns.SMS_THREAD_ID, threadId);
- // Start with conversation hidden - sending a message or saving a draft will change that
- values.put(ConversationColumns.SORT_TIMESTAMP, 0L);
- values.put(ConversationColumns.CURRENT_SELF_ID, selfId);
- values.put(ConversationColumns.PARTICIPANT_COUNT, participants.size());
- values.put(ConversationColumns.INCLUDE_EMAIL_ADDRESS, (hasEmailAddress ? 1 : 0));
- if (archived) {
- values.put(ConversationColumns.ARCHIVE_STATUS, 1);
- }
- if (noNotification) {
- values.put(ConversationColumns.NOTIFICATION_ENABLED, 0);
- }
- if (noVibrate) {
- values.put(ConversationColumns.NOTIFICATION_VIBRATION, 0);
- }
- if (!TextUtils.isEmpty(soundUri)) {
- values.put(ConversationColumns.NOTIFICATION_SOUND_URI, soundUri);
- }
-
- fillParticipantData(values, participants);
-
- final long conversationRowId = dbWrapper.insert(DatabaseHelper.CONVERSATIONS_TABLE, null,
- values);
-
- Assert.isTrue(conversationRowId != -1);
- if (conversationRowId == -1) {
- LogUtil.e(TAG, "BugleDatabaseOperations : failed to insert conversation into table");
- return null;
- }
-
- final String conversationId = Long.toString(conversationRowId);
-
- // Make sure that participants are added for this conversation
- for (final ParticipantData participant : participants) {
- // TODO: Use blocking information
- addParticipantToConversation(dbWrapper, participant, conversationId);
- }
-
- // Now fully resolved participants available can update conversation name / avatar.
- // b/16437575: We cannot use the participants directly, but instead have to call
- // getParticipantsForConversation() to retrieve the actual participants. This is needed
- // because the call to addParticipantToConversation() won't fill up the ParticipantData
- // if the participant already exists in the participant table. For example, say you have
- // an existing conversation with John. Now if you create a new group conversation with
- // Jeff & John with only their phone numbers, then when we try to add John's number to the
- // group conversation, we see that he's already in the participant table, therefore we
- // short-circuit any steps to actually fill out the ParticipantData for John other than
- // just returning his participant id. Eventually, the ParticipantData we have is still the
- // raw data with just the phone number. getParticipantsForConversation(), on the other
- // hand, will fill out all the info for each participant from the participants table.
- updateConversationNameAndAvatarInTransaction(dbWrapper, conversationId,
- getParticipantsForConversation(dbWrapper, conversationId));
-
- return conversationId;
- }
-
- private static void fillParticipantData(final ContentValues values,
- final List<ParticipantData> participants) {
- if (participants != null && !participants.isEmpty()) {
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(participants);
- values.put(ConversationColumns.ICON, avatarUri.toString());
-
- long contactId;
- String lookupKey;
- String destination;
- if (participants.size() == 1) {
- final ParticipantData firstParticipant = participants.get(0);
- contactId = firstParticipant.getContactId();
- lookupKey = firstParticipant.getLookupKey();
- destination = firstParticipant.getNormalizedDestination();
- } else {
- contactId = 0;
- lookupKey = null;
- destination = null;
- }
-
- values.put(ConversationColumns.PARTICIPANT_CONTACT_ID, contactId);
- values.put(ConversationColumns.PARTICIPANT_LOOKUP_KEY, lookupKey);
- values.put(ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION, destination);
- }
- }
-
- /**
- * Delete conversation and associated messages/parts
- */
- @DoesNotRunOnMainThread
- public static boolean deleteConversation(final DatabaseWrapper dbWrapper,
- final String conversationId, final long cutoffTimestamp) {
- Assert.isNotMainThread();
- dbWrapper.beginTransaction();
- boolean conversationDeleted = false;
- boolean conversationMessagesDeleted = false;
- try {
- // Delete existing messages
- if (cutoffTimestamp == Long.MAX_VALUE) {
- // Delete parts and messages
- dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
- MessageColumns.CONVERSATION_ID + "=?", new String[] { conversationId });
- conversationMessagesDeleted = true;
- } else {
- // Delete all messages prior to the cutoff
- dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
- MessageColumns.CONVERSATION_ID + "=? AND "
- + MessageColumns.RECEIVED_TIMESTAMP + "<=?",
- new String[] { conversationId, Long.toString(cutoffTimestamp) });
-
- // Delete any draft message. The delete above may not always include the draft,
- // because under certain scenarios (e.g. sending messages in progress), the draft
- // timestamp can be larger than the cutoff time, which is generally the conversation
- // sort timestamp. Because of how the sms/mms provider works on some newer
- // devices, it's important that we never delete all the messages in a conversation
- // without also deleting the conversation itself (see b/20262204 for details).
- dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
- MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
- new String[] {
- Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
- conversationId
- });
-
- // Check to see if there are any messages left in the conversation
- final long count = dbWrapper.queryNumEntries(DatabaseHelper.MESSAGES_TABLE,
- MessageColumns.CONVERSATION_ID + "=?", new String[] { conversationId });
- conversationMessagesDeleted = (count == 0);
-
- // Log detail information if there are still messages left in the conversation
- if (!conversationMessagesDeleted) {
- final long maxTimestamp =
- getConversationMaxTimestamp(dbWrapper, conversationId);
- LogUtil.w(TAG, "BugleDatabaseOperations:"
- + " cannot delete all messages in a conversation"
- + ", after deletion: count=" + count
- + ", max timestamp=" + maxTimestamp
- + ", cutoff timestamp=" + cutoffTimestamp);
- }
- }
-
- if (conversationMessagesDeleted) {
- // Delete conversation row
- final int count = dbWrapper.delete(DatabaseHelper.CONVERSATIONS_TABLE,
- ConversationColumns._ID + "=?", new String[] { conversationId });
- conversationDeleted = (count > 0);
- }
- dbWrapper.setTransactionSuccessful();
- } finally {
- dbWrapper.endTransaction();
- }
- return conversationDeleted;
- }
-
- private static final String MAX_RECEIVED_TIMESTAMP =
- "MAX(" + MessageColumns.RECEIVED_TIMESTAMP + ")";
- /**
- * Get the max received timestamp of a conversation's messages
- */
- private static long getConversationMaxTimestamp(final DatabaseWrapper dbWrapper,
- final String conversationId) {
- final Cursor cursor = dbWrapper.query(
- DatabaseHelper.MESSAGES_TABLE,
- new String[]{ MAX_RECEIVED_TIMESTAMP },
- MessageColumns.CONVERSATION_ID + "=?",
- new String[]{ conversationId },
- null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- return cursor.getLong(0);
- }
- } finally {
- cursor.close();
- }
- }
- return 0;
- }
-
- @DoesNotRunOnMainThread
- public static void updateConversationMetadataInTransaction(final DatabaseWrapper dbWrapper,
- final String conversationId, final String messageId, final long latestTimestamp,
- final boolean keepArchived, final String smsServiceCenter,
- final boolean shouldAutoSwitchSelfId) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
-
- final ContentValues values = new ContentValues();
- values.put(ConversationColumns.LATEST_MESSAGE_ID, messageId);
- values.put(ConversationColumns.SORT_TIMESTAMP, latestTimestamp);
- if (!TextUtils.isEmpty(smsServiceCenter)) {
- values.put(ConversationColumns.SMS_SERVICE_CENTER, smsServiceCenter);
- }
-
- // When the conversation gets updated with new messages, unarchive the conversation unless
- // the sender is blocked, or we have been told to keep it archived.
- if (!keepArchived) {
- values.put(ConversationColumns.ARCHIVE_STATUS, 0);
- }
-
- final MessageData message = readMessage(dbWrapper, messageId);
- addSnippetTextAndPreviewToContentValues(message, false /* showDraft */, values);
-
- if (shouldAutoSwitchSelfId) {
- addSelfIdAutoSwitchInfoToContentValues(dbWrapper, message, conversationId, values);
- }
-
- // Conversation always exists as this method is called from ActionService only after
- // reading and if necessary creating the conversation.
- updateConversationRow(dbWrapper, conversationId, values);
-
- if (shouldAutoSwitchSelfId && OsUtil.isAtLeastL_MR1()) {
- // Normally, the draft message compose UI trusts its UI state for providing up-to-date
- // conversation self id. Therefore, notify UI through local broadcast receiver about
- // this external change so the change can be properly reflected.
- UIIntents.get().broadcastConversationSelfIdChange(dbWrapper.getContext(),
- conversationId, getConversationSelfId(dbWrapper, conversationId));
- }
- }
-
- @DoesNotRunOnMainThread
- public static void updateConversationMetadataInTransaction(final DatabaseWrapper db,
- final String conversationId, final String messageId, final long latestTimestamp,
- final boolean keepArchived, final boolean shouldAutoSwitchSelfId) {
- Assert.isNotMainThread();
- updateConversationMetadataInTransaction(
- db, conversationId, messageId, latestTimestamp, keepArchived, null,
- shouldAutoSwitchSelfId);
- }
-
- @DoesNotRunOnMainThread
- public static void updateConversationArchiveStatusInTransaction(final DatabaseWrapper dbWrapper,
- final String conversationId, final boolean isArchived) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- final ContentValues values = new ContentValues();
- values.put(ConversationColumns.ARCHIVE_STATUS, isArchived ? 1 : 0);
- updateConversationRowIfExists(dbWrapper, conversationId, values);
- }
-
- static void addSnippetTextAndPreviewToContentValues(final MessageData message,
- final boolean showDraft, final ContentValues values) {
- values.put(ConversationColumns.SHOW_DRAFT, showDraft ? 1 : 0);
- values.put(ConversationColumns.SNIPPET_TEXT, message.getMessageText());
- values.put(ConversationColumns.SUBJECT_TEXT, message.getMmsSubject());
-
- String type = null;
- String uriString = null;
- for (final MessagePartData part : message.getParts()) {
- if (part.isAttachment() &&
- ContentType.isConversationListPreviewableType(part.getContentType())) {
- uriString = part.getContentUri().toString();
- type = part.getContentType();
- break;
- }
- }
- values.put(ConversationColumns.PREVIEW_CONTENT_TYPE, type);
- values.put(ConversationColumns.PREVIEW_URI, uriString);
- }
-
- /**
- * Adds self-id auto switch info for a conversation if the last message has a different
- * subscription than the conversation's.
- * @return true if self id will need to be changed, false otherwise.
- */
- static boolean addSelfIdAutoSwitchInfoToContentValues(final DatabaseWrapper dbWrapper,
- final MessageData message, final String conversationId, final ContentValues values) {
- // Only auto switch conversation self for incoming messages.
- if (!OsUtil.isAtLeastL_MR1() || !message.getIsIncoming()) {
- return false;
- }
-
- final String conversationSelfId = getConversationSelfId(dbWrapper, conversationId);
- final String messageSelfId = message.getSelfId();
-
- if (conversationSelfId == null || messageSelfId == null) {
- return false;
- }
-
- // Get the sub IDs in effect for both the message and the conversation and compare them:
- // 1. If message is unbound (using default sub id), then the message was sent with
- // pre-MSIM support. Don't auto-switch because we don't know the subscription for the
- // message.
- // 2. If message is bound,
- // i. If conversation is unbound, use the system default sub id as its effective sub.
- // ii. If conversation is bound, use its subscription directly.
- // Compare the message sub id with the conversation's effective sub id. If they are
- // different, auto-switch the conversation to the message's sub.
- final ParticipantData conversationSelf = getExistingParticipant(dbWrapper,
- conversationSelfId);
- final ParticipantData messageSelf = getExistingParticipant(dbWrapper, messageSelfId);
- if (!messageSelf.isActiveSubscription()) {
- // Don't switch if the message subscription is no longer active.
- return false;
- }
- final int messageSubId = messageSelf.getSubId();
- if (messageSubId == ParticipantData.DEFAULT_SELF_SUB_ID) {
- return false;
- }
-
- final int conversationEffectiveSubId =
- PhoneUtils.getDefault().getEffectiveSubId(conversationSelf.getSubId());
-
- if (conversationEffectiveSubId != messageSubId) {
- return addConversationSelfIdToContentValues(dbWrapper, messageSelf.getId(), values);
- }
- return false;
- }
-
- /**
- * Adds conversation self id updates to ContentValues given. This performs check on the selfId
- * to ensure it's valid and active.
- * @return true if self id will need to be changed, false otherwise.
- */
- static boolean addConversationSelfIdToContentValues(final DatabaseWrapper dbWrapper,
- final String selfId, final ContentValues values) {
- // Make sure the selfId passed in is valid and active.
- final String selection = ParticipantColumns._ID + "=? AND " +
- ParticipantColumns.SIM_SLOT_ID + "<>?";
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
- new String[] { ParticipantColumns._ID }, selection,
- new String[] { selfId, String.valueOf(ParticipantData.INVALID_SLOT_ID) },
- null, null, null);
-
- if (cursor != null && cursor.getCount() > 0) {
- values.put(ConversationColumns.CURRENT_SELF_ID, selfId);
- return true;
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return false;
- }
-
- private static void updateConversationDraftSnippetAndPreviewInTransaction(
- final DatabaseWrapper dbWrapper, final String conversationId,
- final MessageData draftMessage) {
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
-
- long sortTimestamp = 0L;
- Cursor cursor = null;
- try {
- // Check to find the latest message in the conversation
- cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
- REFRESH_CONVERSATION_MESSAGE_PROJECTION,
- MessageColumns.CONVERSATION_ID + "=?",
- new String[]{conversationId}, null, null,
- MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
-
- if (cursor.moveToFirst()) {
- sortTimestamp = cursor.getLong(1);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
-
- final ContentValues values = new ContentValues();
- if (draftMessage == null || !draftMessage.hasContent()) {
- values.put(ConversationColumns.SHOW_DRAFT, 0);
- values.put(ConversationColumns.DRAFT_SNIPPET_TEXT, "");
- values.put(ConversationColumns.DRAFT_SUBJECT_TEXT, "");
- values.put(ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE, "");
- values.put(ConversationColumns.DRAFT_PREVIEW_URI, "");
- } else {
- sortTimestamp = Math.max(sortTimestamp, draftMessage.getReceivedTimeStamp());
- values.put(ConversationColumns.SHOW_DRAFT, 1);
- values.put(ConversationColumns.DRAFT_SNIPPET_TEXT, draftMessage.getMessageText());
- values.put(ConversationColumns.DRAFT_SUBJECT_TEXT, draftMessage.getMmsSubject());
- String type = null;
- String uriString = null;
- for (final MessagePartData part : draftMessage.getParts()) {
- if (part.isAttachment() &&
- ContentType.isConversationListPreviewableType(part.getContentType())) {
- uriString = part.getContentUri().toString();
- type = part.getContentType();
- break;
- }
- }
- values.put(ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE, type);
- values.put(ConversationColumns.DRAFT_PREVIEW_URI, uriString);
- }
- values.put(ConversationColumns.SORT_TIMESTAMP, sortTimestamp);
- // Called in transaction after reading conversation row
- updateConversationRow(dbWrapper, conversationId, values);
- }
-
- @DoesNotRunOnMainThread
- public static boolean updateConversationRowIfExists(final DatabaseWrapper dbWrapper,
- final String conversationId, final ContentValues values) {
- Assert.isNotMainThread();
- return updateRowIfExists(dbWrapper, DatabaseHelper.CONVERSATIONS_TABLE,
- ConversationColumns._ID, conversationId, values);
- }
-
- @DoesNotRunOnMainThread
- public static void updateConversationRow(final DatabaseWrapper dbWrapper,
- final String conversationId, final ContentValues values) {
- Assert.isNotMainThread();
- final boolean exists = updateConversationRowIfExists(dbWrapper, conversationId, values);
- Assert.isTrue(exists);
- }
-
- @DoesNotRunOnMainThread
- public static boolean updateMessageRowIfExists(final DatabaseWrapper dbWrapper,
- final String messageId, final ContentValues values) {
- Assert.isNotMainThread();
- return updateRowIfExists(dbWrapper, DatabaseHelper.MESSAGES_TABLE, MessageColumns._ID,
- messageId, values);
- }
-
- @DoesNotRunOnMainThread
- public static void updateMessageRow(final DatabaseWrapper dbWrapper,
- final String messageId, final ContentValues values) {
- Assert.isNotMainThread();
- final boolean exists = updateMessageRowIfExists(dbWrapper, messageId, values);
- Assert.isTrue(exists);
- }
-
- @DoesNotRunOnMainThread
- public static boolean updatePartRowIfExists(final DatabaseWrapper dbWrapper,
- final String partId, final ContentValues values) {
- Assert.isNotMainThread();
- return updateRowIfExists(dbWrapper, DatabaseHelper.PARTS_TABLE, PartColumns._ID,
- partId, values);
- }
-
- /**
- * Returns the default conversation name based on its participants.
- */
- private static String getDefaultConversationName(final List<ParticipantData> participants) {
- return ConversationListItemData.generateConversationName(participants);
- }
-
- /**
- * Updates a given conversation's name based on its participants.
- */
- @DoesNotRunOnMainThread
- public static void updateConversationNameAndAvatarInTransaction(
- final DatabaseWrapper dbWrapper, final String conversationId) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
-
- final ArrayList<ParticipantData> participants =
- getParticipantsForConversation(dbWrapper, conversationId);
- updateConversationNameAndAvatarInTransaction(dbWrapper, conversationId, participants);
- }
-
- /**
- * Updates a given conversation's name based on its participants.
- */
- private static void updateConversationNameAndAvatarInTransaction(
- final DatabaseWrapper dbWrapper, final String conversationId,
- final List<ParticipantData> participants) {
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
-
- final ContentValues values = new ContentValues();
- values.put(ConversationColumns.NAME,
- getDefaultConversationName(participants));
-
- fillParticipantData(values, participants);
-
- // Used by background thread when refreshing conversation so conversation could be deleted.
- updateConversationRowIfExists(dbWrapper, conversationId, values);
-
- WidgetConversationProvider.notifyConversationRenamed(Factory.get().getApplicationContext(),
- conversationId);
- }
-
- /**
- * Updates a given conversation's self id.
- */
- @DoesNotRunOnMainThread
- public static void updateConversationSelfIdInTransaction(
- final DatabaseWrapper dbWrapper, final String conversationId, final String selfId) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- final ContentValues values = new ContentValues();
- if (addConversationSelfIdToContentValues(dbWrapper, selfId, values)) {
- updateConversationRowIfExists(dbWrapper, conversationId, values);
- }
- }
-
- @DoesNotRunOnMainThread
- public static String getConversationSelfId(final DatabaseWrapper dbWrapper,
- final String conversationId) {
- Assert.isNotMainThread();
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
- new String[] { ConversationColumns.CURRENT_SELF_ID },
- ConversationColumns._ID + "=?",
- new String[] { conversationId },
- null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- return cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return null;
- }
-
- /**
- * Frees up memory associated with phone number to participant id matching.
- */
- @DoesNotRunOnMainThread
- public static void clearParticipantIdCache() {
- Assert.isNotMainThread();
- synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
- sNormalizedPhoneNumberToParticipantIdCache.clear();
- }
- }
-
- @DoesNotRunOnMainThread
- public static ArrayList<String> getRecipientsForConversation(final DatabaseWrapper dbWrapper,
- final String conversationId) {
- Assert.isNotMainThread();
- final ArrayList<ParticipantData> participants =
- getParticipantsForConversation(dbWrapper, conversationId);
-
- final ArrayList<String> recipients = new ArrayList<String>();
- for (final ParticipantData participant : participants) {
- recipients.add(participant.getSendDestination());
- }
-
- return recipients;
- }
-
- @DoesNotRunOnMainThread
- public static String getSmsServiceCenterForConversation(final DatabaseWrapper dbWrapper,
- final String conversationId) {
- Assert.isNotMainThread();
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
- new String[] { ConversationColumns.SMS_SERVICE_CENTER },
- ConversationColumns._ID + "=?",
- new String[] { conversationId },
- null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- return cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return null;
- }
-
- @DoesNotRunOnMainThread
- public static ParticipantData getExistingParticipant(final DatabaseWrapper dbWrapper,
- final String participantId) {
- Assert.isNotMainThread();
- ParticipantData participant = null;
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
- ParticipantData.ParticipantsQuery.PROJECTION,
- ParticipantColumns._ID + " =?",
- new String[] { participantId }, null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- participant = ParticipantData.getFromCursor(cursor);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return participant;
- }
-
- static int getSelfSubscriptionId(final DatabaseWrapper dbWrapper,
- final String selfParticipantId) {
- final ParticipantData selfParticipant = BugleDatabaseOperations.getExistingParticipant(
- dbWrapper, selfParticipantId);
- if (selfParticipant != null) {
- Assert.isTrue(selfParticipant.isSelf());
- return selfParticipant.getSubId();
- }
- return ParticipantData.DEFAULT_SELF_SUB_ID;
- }
-
- @VisibleForTesting
- @DoesNotRunOnMainThread
- public static ArrayList<ParticipantData> getParticipantsForConversation(
- final DatabaseWrapper dbWrapper, final String conversationId) {
- Assert.isNotMainThread();
- final ArrayList<ParticipantData> participants =
- new ArrayList<ParticipantData>();
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
- ParticipantData.ParticipantsQuery.PROJECTION,
- ParticipantColumns._ID + " IN ( " + "SELECT "
- + ConversationParticipantsColumns.PARTICIPANT_ID + " AS "
- + ParticipantColumns._ID
- + " FROM " + DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE
- + " WHERE " + ConversationParticipantsColumns.CONVERSATION_ID + " =? )",
- new String[] { conversationId }, null, null, null);
-
- while (cursor.moveToNext()) {
- participants.add(ParticipantData.getFromCursor(cursor));
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return participants;
- }
-
- @DoesNotRunOnMainThread
- public static MessageData readMessage(final DatabaseWrapper dbWrapper, final String messageId) {
- Assert.isNotMainThread();
- final MessageData message = readMessageData(dbWrapper, messageId);
- if (message != null) {
- readMessagePartsData(dbWrapper, message, false);
- }
- return message;
- }
-
- @VisibleForTesting
- static MessagePartData readMessagePartData(final DatabaseWrapper dbWrapper,
- final String partId) {
- MessagePartData messagePartData = null;
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE,
- MessagePartData.getProjection(), PartColumns._ID + "=?",
- new String[] { partId }, null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- messagePartData = MessagePartData.createFromCursor(cursor);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return messagePartData;
- }
-
- @DoesNotRunOnMainThread
- public static MessageData readMessageData(final DatabaseWrapper dbWrapper,
- final Uri smsMessageUri) {
- Assert.isNotMainThread();
- MessageData message = null;
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(), MessageColumns.SMS_MESSAGE_URI + "=?",
- new String[] { smsMessageUri.toString() }, null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- message = new MessageData();
- message.bind(cursor);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return message;
- }
-
- @DoesNotRunOnMainThread
- public static MessageData readMessageData(final DatabaseWrapper dbWrapper,
- final String messageId) {
- Assert.isNotMainThread();
- MessageData message = null;
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(), MessageColumns._ID + "=?",
- new String[] { messageId }, null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- message = new MessageData();
- message.bind(cursor);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return message;
- }
-
- /**
- * Read all the parts for a message
- * @param dbWrapper database
- * @param message read parts for this message
- * @param checkAttachmentFilesExist check each attachment file and only include if file exists
- */
- private static void readMessagePartsData(final DatabaseWrapper dbWrapper,
- final MessageData message, final boolean checkAttachmentFilesExist) {
- final ContentResolver contentResolver =
- Factory.get().getApplicationContext().getContentResolver();
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE,
- MessagePartData.getProjection(), PartColumns.MESSAGE_ID + "=?",
- new String[] { message.getMessageId() }, null, null, null);
- while (cursor.moveToNext()) {
- final MessagePartData messagePartData = MessagePartData.createFromCursor(cursor);
- if (checkAttachmentFilesExist && messagePartData.isAttachment() &&
- !UriUtil.isBugleAppResource(messagePartData.getContentUri())) {
- try {
- // Test that the file exists before adding the attachment to the draft
- final ParcelFileDescriptor fileDescriptor =
- contentResolver.openFileDescriptor(
- messagePartData.getContentUri(), "r");
- if (fileDescriptor != null) {
- fileDescriptor.close();
- message.addPart(messagePartData);
- }
- } catch (final IOException e) {
- // The attachment's temp storage no longer exists, just ignore the file
- } catch (final SecurityException e) {
- // Likely thrown by openFileDescriptor due to an expired access grant.
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, "uri: " + messagePartData.getContentUri());
- }
- }
- } else {
- message.addPart(messagePartData);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- /**
- * Write a message part to our local database
- *
- * @param dbWrapper The database
- * @param messagePart The message part to insert
- * @return The row id of the newly inserted part
- */
- static String insertNewMessagePartInTransaction(final DatabaseWrapper dbWrapper,
- final MessagePartData messagePart, final String conversationId) {
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- Assert.isTrue(!TextUtils.isEmpty(messagePart.getMessageId()));
-
- // Insert a new part row
- final SQLiteStatement insert = messagePart.getInsertStatement(dbWrapper, conversationId);
- final long rowNumber = insert.executeInsert();
-
- Assert.inRange(rowNumber, 0, Long.MAX_VALUE);
- final String partId = Long.toString(rowNumber);
-
- // Update the part id
- messagePart.updatePartId(partId);
-
- return partId;
- }
-
- /**
- * Insert a message and its parts into the table
- */
- @DoesNotRunOnMainThread
- public static void insertNewMessageInTransaction(final DatabaseWrapper dbWrapper,
- final MessageData message) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
-
- // Insert message row
- final SQLiteStatement insert = message.getInsertStatement(dbWrapper);
- final long rowNumber = insert.executeInsert();
-
- Assert.inRange(rowNumber, 0, Long.MAX_VALUE);
- final String messageId = Long.toString(rowNumber);
- message.updateMessageId(messageId);
- // Insert new parts
- for (final MessagePartData messagePart : message.getParts()) {
- messagePart.updateMessageId(messageId);
- insertNewMessagePartInTransaction(dbWrapper, messagePart, message.getConversationId());
- }
- }
-
- /**
- * Update a message and add its parts into the table
- */
- @DoesNotRunOnMainThread
- public static void updateMessageInTransaction(final DatabaseWrapper dbWrapper,
- final MessageData message) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- final String messageId = message.getMessageId();
- // Check message still exists (sms sync or delete might have purged it)
- final MessageData current = BugleDatabaseOperations.readMessage(dbWrapper, messageId);
- if (current != null) {
- // Delete existing message parts)
- deletePartsForMessage(dbWrapper, message.getMessageId());
- // Insert new parts
- for (final MessagePartData messagePart : message.getParts()) {
- messagePart.updatePartId(null);
- messagePart.updateMessageId(message.getMessageId());
- insertNewMessagePartInTransaction(dbWrapper, messagePart,
- message.getConversationId());
- }
- // Update message row
- final ContentValues values = new ContentValues();
- message.populate(values);
- updateMessageRowIfExists(dbWrapper, message.getMessageId(), values);
- }
- }
-
- @DoesNotRunOnMainThread
- public static void updateMessageAndPartsInTransaction(final DatabaseWrapper dbWrapper,
- final MessageData message, final List<MessagePartData> partsToUpdate) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- final ContentValues values = new ContentValues();
- for (final MessagePartData messagePart : partsToUpdate) {
- values.clear();
- messagePart.populate(values);
- updatePartRowIfExists(dbWrapper, messagePart.getPartId(), values);
- }
- values.clear();
- message.populate(values);
- updateMessageRowIfExists(dbWrapper, message.getMessageId(), values);
- }
-
- /**
- * Delete all parts for a message
- */
- static void deletePartsForMessage(final DatabaseWrapper dbWrapper,
- final String messageId) {
- final int cnt = dbWrapper.delete(DatabaseHelper.PARTS_TABLE,
- PartColumns.MESSAGE_ID + " =?",
- new String[] { messageId });
- Assert.inRange(cnt, 0, Integer.MAX_VALUE);
- }
-
- /**
- * Delete one message and update the conversation (if necessary).
- *
- * @return number of rows deleted (should be 1 or 0).
- */
- @DoesNotRunOnMainThread
- public static int deleteMessage(final DatabaseWrapper dbWrapper, final String messageId) {
- Assert.isNotMainThread();
- dbWrapper.beginTransaction();
- try {
- // Read message to find out which conversation it is in
- final MessageData message = BugleDatabaseOperations.readMessage(dbWrapper, messageId);
-
- int count = 0;
- if (message != null) {
- final String conversationId = message.getConversationId();
- // Delete message
- count = dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
- MessageColumns._ID + "=?", new String[] { messageId });
-
- if (!deleteConversationIfEmptyInTransaction(dbWrapper, conversationId)) {
- // TODO: Should we leave the conversation sort timestamp alone?
- refreshConversationMetadataInTransaction(dbWrapper, conversationId,
- false/* shouldAutoSwitchSelfId */, false/*archived*/);
- }
- }
- dbWrapper.setTransactionSuccessful();
- return count;
- } finally {
- dbWrapper.endTransaction();
- }
- }
-
- /**
- * Deletes the conversation if there are zero non-draft messages left.
- * <p>
- * This is necessary because the telephony database has a trigger that deletes threads after
- * their last message is deleted. We need to ensure that if a thread goes away, we also delete
- * the conversation in Bugle. We don't store draft messages in telephony, so we ignore those
- * when querying for the # of messages in the conversation.
- *
- * @return true if the conversation was deleted
- */
- @DoesNotRunOnMainThread
- public static boolean deleteConversationIfEmptyInTransaction(final DatabaseWrapper dbWrapper,
- final String conversationId) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- Cursor cursor = null;
- try {
- // TODO: The refreshConversationMetadataInTransaction method below uses this
- // same query; maybe they should share this logic?
-
- // Check to see if there are any (non-draft) messages in the conversation
- cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
- REFRESH_CONVERSATION_MESSAGE_PROJECTION,
- MessageColumns.CONVERSATION_ID + "=? AND " +
- MessageColumns.STATUS + "!=" + MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
- new String[] { conversationId }, null, null,
- MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
- if (cursor.getCount() == 0) {
- dbWrapper.delete(DatabaseHelper.CONVERSATIONS_TABLE,
- ConversationColumns._ID + "=?", new String[] { conversationId });
- LogUtil.i(TAG,
- "BugleDatabaseOperations: Deleted empty conversation " + conversationId);
- return true;
- } else {
- return false;
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private static final String[] REFRESH_CONVERSATION_MESSAGE_PROJECTION = new String[] {
- MessageColumns._ID,
- MessageColumns.RECEIVED_TIMESTAMP,
- MessageColumns.SENDER_PARTICIPANT_ID
- };
-
- /**
- * Update conversation snippet, timestamp and optionally self id to match latest message in
- * conversation.
- */
- @DoesNotRunOnMainThread
- public static void refreshConversationMetadataInTransaction(final DatabaseWrapper dbWrapper,
- final String conversationId, final boolean shouldAutoSwitchSelfId,
- boolean keepArchived) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- Cursor cursor = null;
- try {
- // Check to see if there are any (non-draft) messages in the conversation
- cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
- REFRESH_CONVERSATION_MESSAGE_PROJECTION,
- MessageColumns.CONVERSATION_ID + "=? AND " +
- MessageColumns.STATUS + "!=" + MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
- new String[] { conversationId }, null, null,
- MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
-
- if (cursor.moveToFirst()) {
- // Refresh latest message in conversation
- final String latestMessageId = cursor.getString(0);
- final long latestMessageTimestamp = cursor.getLong(1);
- final String senderParticipantId = cursor.getString(2);
- final boolean senderBlocked = isBlockedParticipant(dbWrapper, senderParticipantId);
- updateConversationMetadataInTransaction(dbWrapper, conversationId,
- latestMessageId, latestMessageTimestamp, senderBlocked || keepArchived,
- shouldAutoSwitchSelfId);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- /**
- * When moving/removing an existing message update conversation metadata if necessary
- * @param dbWrapper db wrapper
- * @param conversationId conversation to modify
- * @param messageId message that is leaving the conversation
- * @param shouldAutoSwitchSelfId should we try to auto-switch the conversation's self-id as a
- * result of this call when we see a new latest message?
- * @param keepArchived should we keep the conversation archived despite refresh
- */
- @DoesNotRunOnMainThread
- public static void maybeRefreshConversationMetadataInTransaction(
- final DatabaseWrapper dbWrapper, final String conversationId, final String messageId,
- final boolean shouldAutoSwitchSelfId, final boolean keepArchived) {
- Assert.isNotMainThread();
- boolean refresh = true;
- if (!TextUtils.isEmpty(messageId)) {
- refresh = false;
- // Look for an existing conversation in the db with this conversation id
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
- new String[] { ConversationColumns.LATEST_MESSAGE_ID },
- ConversationColumns._ID + "=?",
- new String[] { conversationId },
- null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- refresh = TextUtils.equals(cursor.getString(0), messageId);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- if (refresh) {
- // TODO: I think it is okay to delete the conversation if it is empty...
- refreshConversationMetadataInTransaction(dbWrapper, conversationId,
- shouldAutoSwitchSelfId, keepArchived);
- }
- }
-
-
-
- // SQL statement to query latest message if for particular conversation
- private static final String QUERY_CONVERSATIONS_LATEST_MESSAGE_SQL = "SELECT "
- + ConversationColumns.LATEST_MESSAGE_ID + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
- + " WHERE " + ConversationColumns._ID + "=? LIMIT 1";
-
- /**
- * Note this is not thread safe so callers need to make sure they own the wrapper + statements
- * while they call this and use the returned value.
- */
- @DoesNotRunOnMainThread
- public static SQLiteStatement getQueryConversationsLatestMessageStatement(
- final DatabaseWrapper db, final String conversationId) {
- Assert.isNotMainThread();
- final SQLiteStatement query = db.getStatementInTransaction(
- DatabaseWrapper.INDEX_QUERY_CONVERSATIONS_LATEST_MESSAGE,
- QUERY_CONVERSATIONS_LATEST_MESSAGE_SQL);
- query.clearBindings();
- query.bindString(1, conversationId);
- return query;
- }
-
- // SQL statement to query latest message if for particular conversation
- private static final String QUERY_MESSAGES_LATEST_MESSAGE_SQL = "SELECT "
- + MessageColumns._ID + " FROM " + DatabaseHelper.MESSAGES_TABLE
- + " WHERE " + MessageColumns.CONVERSATION_ID + "=? ORDER BY "
- + MessageColumns.RECEIVED_TIMESTAMP + " DESC LIMIT 1";
-
- /**
- * Note this is not thread safe so callers need to make sure they own the wrapper + statements
- * while they call this and use the returned value.
- */
- @DoesNotRunOnMainThread
- public static SQLiteStatement getQueryMessagesLatestMessageStatement(
- final DatabaseWrapper db, final String conversationId) {
- Assert.isNotMainThread();
- final SQLiteStatement query = db.getStatementInTransaction(
- DatabaseWrapper.INDEX_QUERY_MESSAGES_LATEST_MESSAGE,
- QUERY_MESSAGES_LATEST_MESSAGE_SQL);
- query.clearBindings();
- query.bindString(1, conversationId);
- return query;
- }
-
- /**
- * Update conversation metadata if necessary
- * @param dbWrapper db wrapper
- * @param conversationId conversation to modify
- * @param shouldAutoSwitchSelfId should we try to auto-switch the conversation's self-id as a
- * result of this call when we see a new latest message?
- * @param keepArchived if the conversation should be kept archived
- */
- @DoesNotRunOnMainThread
- public static void maybeRefreshConversationMetadataInTransaction(
- final DatabaseWrapper dbWrapper, final String conversationId,
- final boolean shouldAutoSwitchSelfId, boolean keepArchived) {
- Assert.isNotMainThread();
- String currentLatestMessageId = null;
- String latestMessageId = null;
- try {
- final SQLiteStatement currentLatestMessageIdSql =
- getQueryConversationsLatestMessageStatement(dbWrapper, conversationId);
- currentLatestMessageId = currentLatestMessageIdSql.simpleQueryForString();
-
- final SQLiteStatement latestMessageIdSql =
- getQueryMessagesLatestMessageStatement(dbWrapper, conversationId);
- latestMessageId = latestMessageIdSql.simpleQueryForString();
- } catch (final SQLiteDoneException e) {
- LogUtil.e(TAG, "BugleDatabaseOperations: Query for latest message failed", e);
- }
-
- if (TextUtils.isEmpty(currentLatestMessageId) ||
- !TextUtils.equals(currentLatestMessageId, latestMessageId)) {
- refreshConversationMetadataInTransaction(dbWrapper, conversationId,
- shouldAutoSwitchSelfId, keepArchived);
- }
- }
-
- static boolean getConversationExists(final DatabaseWrapper dbWrapper,
- final String conversationId) {
- // Look for an existing conversation in the db with this conversation id
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
- new String[] { /* No projection */},
- ConversationColumns._ID + "=?",
- new String[] { conversationId },
- null, null, null);
- return cursor.getCount() == 1;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- /** Preserve parts in message but clear the stored draft */
- public static final int UPDATE_MODE_CLEAR_DRAFT = 1;
- /** Add the message as a draft */
- public static final int UPDATE_MODE_ADD_DRAFT = 2;
-
- /**
- * Update draft message for specified conversation
- * @param dbWrapper local database (wrapped)
- * @param conversationId conversation to update
- * @param message Optional message to preserve attachments for (either as draft or for
- * sending)
- * @param updateMode either {@link #UPDATE_MODE_CLEAR_DRAFT} or
- * {@link #UPDATE_MODE_ADD_DRAFT}
- * @return message id of newly written draft (else null)
- */
- @DoesNotRunOnMainThread
- public static String updateDraftMessageData(final DatabaseWrapper dbWrapper,
- final String conversationId, @Nullable final MessageData message,
- final int updateMode) {
- Assert.isNotMainThread();
- Assert.notNull(conversationId);
- Assert.inRange(updateMode, UPDATE_MODE_CLEAR_DRAFT, UPDATE_MODE_ADD_DRAFT);
- String messageId = null;
- Cursor cursor = null;
- dbWrapper.beginTransaction();
- try {
- // Find all draft parts for the current conversation
- final SimpleArrayMap<Uri, MessagePartData> currentDraftParts = new SimpleArrayMap<>();
- cursor = dbWrapper.query(DatabaseHelper.DRAFT_PARTS_VIEW,
- MessagePartData.getProjection(),
- MessageColumns.CONVERSATION_ID + " =?",
- new String[] { conversationId }, null, null, null);
- while (cursor.moveToNext()) {
- final MessagePartData part = MessagePartData.createFromCursor(cursor);
- if (part.isAttachment()) {
- currentDraftParts.put(part.getContentUri(), part);
- }
- }
- // Optionally, preserve attachments for "message"
- final boolean conversationExists = getConversationExists(dbWrapper, conversationId);
- if (message != null && conversationExists) {
- for (final MessagePartData part : message.getParts()) {
- if (part.isAttachment()) {
- currentDraftParts.remove(part.getContentUri());
- }
- }
- }
-
- // Delete orphan content
- for (int index = 0; index < currentDraftParts.size(); index++) {
- final MessagePartData part = currentDraftParts.valueAt(index);
- part.destroySync();
- }
-
- // Delete existing draft (cascade deletes parts)
- dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
- MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
- new String[] {
- Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
- conversationId
- });
-
- // Write new draft
- if (updateMode == UPDATE_MODE_ADD_DRAFT && message != null
- && message.hasContent() && conversationExists) {
- Assert.equals(MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
- message.getStatus());
-
- // Now add draft to message table
- insertNewMessageInTransaction(dbWrapper, message);
- messageId = message.getMessageId();
- }
-
- if (conversationExists) {
- updateConversationDraftSnippetAndPreviewInTransaction(
- dbWrapper, conversationId, message);
-
- if (message != null && message.getSelfId() != null) {
- updateConversationSelfIdInTransaction(dbWrapper, conversationId,
- message.getSelfId());
- }
- }
-
- dbWrapper.setTransactionSuccessful();
- } finally {
- dbWrapper.endTransaction();
- if (cursor != null) {
- cursor.close();
- }
- }
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG,
- "Updated draft message " + messageId + " for conversation " + conversationId);
- }
- return messageId;
- }
-
- /**
- * Read the first draft message associated with this conversation.
- * If none present create an empty (sms) draft message.
- */
- @DoesNotRunOnMainThread
- public static MessageData readDraftMessageData(final DatabaseWrapper dbWrapper,
- final String conversationId, final String conversationSelfId) {
- Assert.isNotMainThread();
- MessageData message = null;
- Cursor cursor = null;
- dbWrapper.beginTransaction();
- try {
- cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(),
- MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
- new String[] {
- Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
- conversationId
- }, null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- message = new MessageData();
- message.bindDraft(cursor, conversationSelfId);
- readMessagePartsData(dbWrapper, message, true);
- // Disconnect draft parts from DB
- for (final MessagePartData part : message.getParts()) {
- part.updatePartId(null);
- part.updateMessageId(null);
- }
- message.updateMessageId(null);
- }
- dbWrapper.setTransactionSuccessful();
- } finally {
- dbWrapper.endTransaction();
- if (cursor != null) {
- cursor.close();
- }
- }
- return message;
- }
-
- // Internal
- private static void addParticipantToConversation(final DatabaseWrapper dbWrapper,
- final ParticipantData participant, final String conversationId) {
- final String participantId = getOrCreateParticipantInTransaction(dbWrapper, participant);
- Assert.notNull(participantId);
-
- // Add the participant to the conversation participants table
- final ContentValues values = new ContentValues();
- values.put(ConversationParticipantsColumns.CONVERSATION_ID, conversationId);
- values.put(ConversationParticipantsColumns.PARTICIPANT_ID, participantId);
- dbWrapper.insert(DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE, null, values);
- }
-
- /**
- * Get string used as canonical recipient for participant cache for sub id
- */
- private static String getCanonicalRecipientFromSubId(final int subId) {
- return "SELF(" + subId + ")";
- }
-
- /**
- * Maps from a sub id or phone number to a participant id if there is one.
- *
- * @return If the participant is available in our cache, or the DB, this returns the
- * participant id for the given subid/phone number. Otherwise it returns null.
- */
- @VisibleForTesting
- private static String getParticipantId(final DatabaseWrapper dbWrapper,
- final int subId, final String canonicalRecipient) {
- // First check our memory cache for the participant Id
- String participantId;
- synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
- participantId = sNormalizedPhoneNumberToParticipantIdCache.get(canonicalRecipient);
- }
-
- if (participantId != null) {
- return participantId;
- }
-
- // This code will only be executed for incremental additions.
- Cursor cursor = null;
- try {
- if (subId != ParticipantData.OTHER_THAN_SELF_SUB_ID) {
- // Now look for an existing participant in the db with this sub id.
- cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
- new String[] {ParticipantColumns._ID},
- ParticipantColumns.SUB_ID + "=?",
- new String[] { Integer.toString(subId) }, null, null, null);
- } else {
- // Look for existing participant with this normalized phone number and no subId.
- cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
- new String[] {ParticipantColumns._ID},
- ParticipantColumns.NORMALIZED_DESTINATION + "=? AND "
- + ParticipantColumns.SUB_ID + "=?",
- new String[] {canonicalRecipient, Integer.toString(subId)},
- null, null, null);
- }
-
- if (cursor.moveToFirst()) {
- // TODO Is this assert correct for multi-sim where a new sim was put in?
- Assert.isTrue(cursor.getCount() == 1);
-
- // We found an existing participant in the database
- participantId = cursor.getString(0);
-
- synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
- // Add it to the cache for next time
- sNormalizedPhoneNumberToParticipantIdCache.put(canonicalRecipient,
- participantId);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return participantId;
- }
-
- @DoesNotRunOnMainThread
- public static ParticipantData getOrCreateSelf(final DatabaseWrapper dbWrapper,
- final int subId) {
- Assert.isNotMainThread();
- ParticipantData participant = null;
- dbWrapper.beginTransaction();
- try {
- final ParticipantData shell = ParticipantData.getSelfParticipant(subId);
- final String participantId = getOrCreateParticipantInTransaction(dbWrapper, shell);
- participant = getExistingParticipant(dbWrapper, participantId);
- dbWrapper.setTransactionSuccessful();
- } finally {
- dbWrapper.endTransaction();
- }
- return participant;
- }
-
- /**
- * Lookup and if necessary create a new participant
- * @param dbWrapper Database wrapper
- * @param participant Participant to find/create
- * @return participantId ParticipantId for existing or newly created participant
- */
- @DoesNotRunOnMainThread
- public static String getOrCreateParticipantInTransaction(final DatabaseWrapper dbWrapper,
- final ParticipantData participant) {
- Assert.isNotMainThread();
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- int subId = ParticipantData.OTHER_THAN_SELF_SUB_ID;
- String participantId = null;
- String canonicalRecipient = null;
- if (participant.isSelf()) {
- subId = participant.getSubId();
- canonicalRecipient = getCanonicalRecipientFromSubId(subId);
- } else {
- canonicalRecipient = participant.getNormalizedDestination();
- }
- Assert.notNull(canonicalRecipient);
- participantId = getParticipantId(dbWrapper, subId, canonicalRecipient);
-
- if (participantId != null) {
- return participantId;
- }
-
- if (!participant.isContactIdResolved()) {
- // Refresh participant's name and avatar with matching contact in CP2.
- ParticipantRefresh.refreshParticipant(dbWrapper, participant);
- }
-
- // Insert the participant into the participants table
- final ContentValues values = participant.toContentValues();
- final long participantRow = dbWrapper.insert(DatabaseHelper.PARTICIPANTS_TABLE, null,
- values);
- participantId = Long.toString(participantRow);
- Assert.notNull(canonicalRecipient);
-
- synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
- // Now that we've inserted it, add it to our cache
- sNormalizedPhoneNumberToParticipantIdCache.put(canonicalRecipient, participantId);
- }
-
- return participantId;
- }
-
- @DoesNotRunOnMainThread
- public static void updateDestination(final DatabaseWrapper dbWrapper,
- final String destination, final boolean blocked) {
- Assert.isNotMainThread();
- final ContentValues values = new ContentValues();
- values.put(ParticipantColumns.BLOCKED, blocked ? 1 : 0);
- dbWrapper.update(DatabaseHelper.PARTICIPANTS_TABLE, values,
- ParticipantColumns.NORMALIZED_DESTINATION + "=? AND " +
- ParticipantColumns.SUB_ID + "=?",
- new String[] { destination, Integer.toString(
- ParticipantData.OTHER_THAN_SELF_SUB_ID) });
- }
-
- @DoesNotRunOnMainThread
- public static String getConversationFromOtherParticipantDestination(
- final DatabaseWrapper db, final String otherDestination) {
- Assert.isNotMainThread();
- Cursor cursor = null;
- try {
- cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
- new String[] { ConversationColumns._ID },
- ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + "=?",
- new String[] { otherDestination }, null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- return cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return null;
- }
-
-
- /**
- * Get a list of conversations that contain any of participants specified.
- */
- private static HashSet<String> getConversationsForParticipants(
- final ArrayList<String> participantIds) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final HashSet<String> conversationIds = new HashSet<String>();
-
- final String selection = ConversationParticipantsColumns.PARTICIPANT_ID + "=?";
- for (final String participantId : participantIds) {
- final String[] selectionArgs = new String[] { participantId };
- final Cursor cursor = db.query(DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE,
- ConversationParticipantsQuery.PROJECTION,
- selection, selectionArgs, null, null, null);
-
- if (cursor != null) {
- try {
- while (cursor.moveToNext()) {
- final String conversationId = cursor.getString(
- ConversationParticipantsQuery.INDEX_CONVERSATION_ID);
- conversationIds.add(conversationId);
- }
- } finally {
- cursor.close();
- }
- }
- }
-
- return conversationIds;
- }
-
- /**
- * Refresh conversation names/avatars based on a list of participants that are changed.
- */
- @DoesNotRunOnMainThread
- public static void refreshConversationsForParticipants(final ArrayList<String> participants) {
- Assert.isNotMainThread();
- final HashSet<String> conversationIds = getConversationsForParticipants(participants);
- if (conversationIds.size() > 0) {
- for (final String conversationId : conversationIds) {
- refreshConversation(conversationId);
- }
-
- MessagingContentProvider.notifyConversationListChanged();
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Number of conversations refreshed:" + conversationIds.size());
- }
- }
- }
-
- /**
- * Refresh conversation names/avatars based on a changed participant.
- */
- @DoesNotRunOnMainThread
- public static void refreshConversationsForParticipant(final String participantId) {
- Assert.isNotMainThread();
- final ArrayList<String> participantList = new ArrayList<String>(1);
- participantList.add(participantId);
- refreshConversationsForParticipants(participantList);
- }
-
- /**
- * Refresh one conversation.
- */
- private static void refreshConversation(final String conversationId) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- db.beginTransaction();
- try {
- BugleDatabaseOperations.updateConversationNameAndAvatarInTransaction(db,
- conversationId);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- MessagingContentProvider.notifyParticipantsChanged(conversationId);
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
- }
-
- @DoesNotRunOnMainThread
- public static boolean updateRowIfExists(final DatabaseWrapper db, final String table,
- final String rowKey, final String rowId, final ContentValues values) {
- Assert.isNotMainThread();
- final StringBuilder sb = new StringBuilder();
- final ArrayList<String> whereValues = new ArrayList<String>(values.size() + 1);
- whereValues.add(rowId);
-
- for (final String key : values.keySet()) {
- if (sb.length() > 0) {
- sb.append(" OR ");
- }
- final Object value = values.get(key);
- sb.append(key);
- if (value != null) {
- sb.append(" IS NOT ?");
- whereValues.add(value.toString());
- } else {
- sb.append(" IS NOT NULL");
- }
- }
-
- final String whereClause = rowKey + "=?" + " AND (" + sb.toString() + ")";
- final String [] whereValuesArray = whereValues.toArray(new String[whereValues.size()]);
- final int count = db.update(table, values, whereClause, whereValuesArray);
- if (count > 1) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Updated more than 1 row " + count + "; " + table +
- " for " + rowKey + " = " + rowId + " (deleted?)");
- }
- Assert.inRange(count, 0, 1);
- return (count >= 0);
- }
-}
diff --git a/src/com/android/messaging/datamodel/BugleNotifications.java b/src/com/android/messaging/datamodel/BugleNotifications.java
deleted file mode 100644
index b796e73..0000000
--- a/src/com/android/messaging/datamodel/BugleNotifications.java
+++ /dev/null
@@ -1,1221 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
-import android.graphics.Typeface;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.WearableExtender;
-import android.support.v4.app.NotificationManagerCompat;
-import android.support.v4.app.RemoteInput;
-import android.support.v4.util.SimpleArrayMap;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-import android.text.style.TextAppearanceSpan;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MessageNotificationState.BundledMessageNotificationState;
-import com.android.messaging.datamodel.MessageNotificationState.ConversationLineInfo;
-import com.android.messaging.datamodel.MessageNotificationState.MultiConversationNotificationState;
-import com.android.messaging.datamodel.MessageNotificationState.MultiMessageNotificationState;
-import com.android.messaging.datamodel.action.MarkAsReadAction;
-import com.android.messaging.datamodel.action.MarkAsSeenAction;
-import com.android.messaging.datamodel.action.RedownloadMmsAction;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.datamodel.media.VideoThumbnailRequest;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.BuglePrefsKeys;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ConversationIdSet;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.NotificationPlayer;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PendingIntentConstants;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.RingtoneUtil;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UriUtil;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Handle posting, updating and removing all conversation notifications.
- *
- * There are currently two main classes of notification and their rules: <p>
- * 1) Messages - {@link MessageNotificationState}. Only one message notification.
- * Unread messages across senders and conversations are coalesced.<p>
- * 2) Failed Messages - {@link MessageNotificationState#checkFailedMesages } Only one failed
- * message. Multiple failures are coalesced.<p>
- *
- * To add a new class of notifications, subclass the NotificationState and add commands which
- * create one and pass into general creation function.
- *
- */
-public class BugleNotifications {
- // Logging
- public static final String TAG = LogUtil.BUGLE_NOTIFICATIONS_TAG;
-
- // Constants to use for update.
- public static final int UPDATE_NONE = 0;
- public static final int UPDATE_MESSAGES = 1;
- public static final int UPDATE_ERRORS = 2;
- public static final int UPDATE_ALL = UPDATE_MESSAGES + UPDATE_ERRORS;
-
- // Constants for notification type used for audio and vibration settings.
- public static final int LOCAL_SMS_NOTIFICATION = 0;
-
- private static final String SMS_NOTIFICATION_TAG = ":sms:";
- private static final String SMS_ERROR_NOTIFICATION_TAG = ":error:";
-
- private static final String WEARABLE_COMPANION_APP_PACKAGE = "com.google.android.wearable.app";
-
- private static final Set<NotificationState> sPendingNotifications =
- new HashSet<NotificationState>();
-
- private static int sWearableImageWidth;
- private static int sWearableImageHeight;
- private static int sIconWidth;
- private static int sIconHeight;
-
- private static boolean sInitialized = false;
-
- private static final Object mLock = new Object();
-
- // sLastMessageDingTime is a map between a conversation id and a time. It's used to keep track
- // of the time we last dinged a message for this conversation. When messages are coming in
- // at flurry, we don't want to over-ding the user.
- private static final SimpleArrayMap<String, Long> sLastMessageDingTime =
- new SimpleArrayMap<String, Long>();
- private static int sTimeBetweenDingsMs;
-
- /**
- * This is the volume at which to play the observable-conversation notification sound,
- * expressed as a fraction of the system notification volume.
- */
- private static final float OBSERVABLE_CONVERSATION_NOTIFICATION_VOLUME = 0.25f;
-
- /**
- * Entry point for posting notifications.
- * Don't call this on the UI thread.
- * @param silent If true, no ring will be played. If false, checks global settings before
- * playing a ringtone
- * @param coverage Indicates which notification types should be checked. Valid values are
- * UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, or UPDATE_ALL
- */
- public static void update(final boolean silent, final int coverage) {
- update(silent, null /* conversationId */, coverage);
- }
-
- /**
- * Entry point for posting notifications.
- * Don't call this on the UI thread.
- * @param silent If true, no ring will be played. If false, checks global settings before
- * playing a ringtone
- * @param conversationId Conversation ID where a new message was received
- * @param coverage Indicates which notification types should be checked. Valid values are
- * UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, or UPDATE_ALL
- */
- public static void update(final boolean silent, final String conversationId,
- final int coverage) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Update: silent = " + silent
- + " conversationId = " + conversationId
- + " coverage = " + coverage);
- }
- Assert.isNotMainThread();
- checkInitialized();
-
- if (!shouldNotify()) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Notifications disabled");
- }
- cancel(PendingIntentConstants.SMS_NOTIFICATION_ID);
- return;
- } else {
- if ((coverage & UPDATE_MESSAGES) != 0) {
- createMessageNotification(silent, conversationId);
- }
- }
- if ((coverage & UPDATE_ERRORS) != 0) {
- MessageNotificationState.checkFailedMessages();
- }
- }
-
- /**
- * Cancel all notifications of a certain type.
- *
- * @param type Message or error notifications from Constants.
- */
- private static synchronized void cancel(final int type) {
- cancel(type, null, false);
- }
-
- /**
- * Cancel all notifications of a certain type.
- *
- * @param type Message or error notifications from Constants.
- * @param conversationId If set, cancel the notification for this
- * conversation only. For message notifications, this only works
- * if the notifications are bundled (group children).
- * @param isBundledNotification True if this notification is part of a
- * notification bundle. This only applies to message notifications,
- * which are bundled together with other message notifications.
- */
- private static synchronized void cancel(final int type, final String conversationId,
- final boolean isBundledNotification) {
- final String notificationTag = buildNotificationTag(type, conversationId,
- isBundledNotification);
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(Factory.get().getApplicationContext());
-
- // Find all pending notifications and cancel them.
- synchronized (sPendingNotifications) {
- final Iterator<NotificationState> iter = sPendingNotifications.iterator();
- while (iter.hasNext()) {
- final NotificationState notifState = iter.next();
- if (notifState.mType == type) {
- notifState.mCanceled = true;
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Canceling pending notification");
- }
- iter.remove();
- }
- }
- }
- notificationManager.cancel(notificationTag, type);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Canceled notifications of type " + type);
- }
-
- // Message notifications for multiple conversations can be grouped together (see comment in
- // createMessageNotification). We need to do bookkeeping to track the current set of
- // notification group children, including removing them when we cancel notifications).
- if (type == PendingIntentConstants.SMS_NOTIFICATION_ID) {
- final Context context = Factory.get().getApplicationContext();
- final ConversationIdSet groupChildIds = getGroupChildIds(context);
-
- if (groupChildIds != null && groupChildIds.size() > 0) {
- // If a conversation is specified, remove just that notification. Otherwise,
- // we're removing the group summary so clear all children.
- if (conversationId != null) {
- groupChildIds.remove(conversationId);
- writeGroupChildIds(context, groupChildIds);
- } else {
- cancelStaleGroupChildren(groupChildIds, null);
- // We'll update the group children preference as we cancel each child,
- // so we don't need to do it here.
- }
- }
- }
- }
-
- /**
- * Cancels stale notifications from the currently active group of
- * notifications. If the {@code state} parameter is an instance of
- * {@link MultiConversationNotificationState} it represents a new
- * notification group. This method will cancel any notifications that were
- * in the old group, but not the new one. If the new notification is not a
- * group, then all existing grouped notifications are cancelled.
- *
- * @param previousGroupChildren Conversation ids for the active notification
- * group
- * @param state New notification state
- */
- private static void cancelStaleGroupChildren(final ConversationIdSet previousGroupChildren,
- final NotificationState state) {
- final ConversationIdSet newChildren = new ConversationIdSet();
- if (state instanceof MultiConversationNotificationState) {
- for (final NotificationState child :
- ((MultiConversationNotificationState) state).mChildren) {
- if (child.mConversationIds != null) {
- newChildren.add(child.mConversationIds.first());
- }
- }
- }
- for (final String childConversationId : previousGroupChildren) {
- if (!newChildren.contains(childConversationId)) {
- cancel(PendingIntentConstants.SMS_NOTIFICATION_ID, childConversationId, true);
- }
- }
- }
-
- /**
- * Returns {@code true} if incoming notifications should display a
- * notification, {@code false} otherwise.
- *
- * @return true if the notification should occur
- */
- private static boolean shouldNotify() {
- // If we're not the default sms app, don't put up any notifications.
- if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
- return false;
- }
-
- // Now check prefs (i.e. settings) to see if the user turned off notifications.
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- final Context context = Factory.get().getApplicationContext();
- final String prefKey = context.getString(R.string.notifications_enabled_pref_key);
- final boolean defaultValue = context.getResources().getBoolean(
- R.bool.notifications_enabled_pref_default);
- return prefs.getBoolean(prefKey, defaultValue);
- }
-
- /**
- * Returns {@code true} if incoming notifications for the given {@link NotificationState}
- * should vibrate the device, {@code false} otherwise.
- *
- * @return true if vibration should be used
- */
- public static boolean shouldVibrate(final NotificationState state) {
- // The notification should vibrate if the global setting is turned on AND
- // the per-conversation setting is turned on (default).
- if (!state.getNotificationVibrate()) {
- return false;
- } else {
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- final Context context = Factory.get().getApplicationContext();
- final String prefKey = context.getString(R.string.notification_vibration_pref_key);
- final boolean defaultValue = context.getResources().getBoolean(
- R.bool.notification_vibration_pref_default);
- return prefs.getBoolean(prefKey, defaultValue);
- }
- }
-
- private static Uri getNotificationRingtoneUriForConversationId(final String conversationId) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final ConversationListItemData convData =
- ConversationListItemData.getExistingConversation(db, conversationId);
- return RingtoneUtil.getNotificationRingtoneUri(
- convData != null ? convData.getNotificationSoundUri() : null);
- }
-
- /**
- * Returns a unique tag to identify a notification.
- *
- * @param name The tag name (in practice, the type)
- * @param conversationId The conversation id (optional)
- */
- private static String buildNotificationTag(final String name,
- final String conversationId) {
- final Context context = Factory.get().getApplicationContext();
- if (conversationId != null) {
- return context.getPackageName() + name + ":" + conversationId;
- } else {
- return context.getPackageName() + name;
- }
- }
-
- /**
- * Returns a unique tag to identify a notification.
- * <p>
- * This delegates to
- * {@link #buildNotificationTag(int, String, boolean)} and can be
- * used when the notification is never bundled (e.g. error notifications).
- */
- static String buildNotificationTag(final int type, final String conversationId) {
- return buildNotificationTag(type, conversationId, false /* bundledNotification */);
- }
-
- /**
- * Returns a unique tag to identify a notification.
- *
- * @param type One of the constants in {@link PendingIntentConstants}
- * @param conversationId The conversation id (where applicable)
- * @param bundledNotification Set to true if this notification will be
- * bundled together with other notifications (e.g. on a wearable
- * device).
- */
- static String buildNotificationTag(final int type, final String conversationId,
- final boolean bundledNotification) {
- String tag = null;
- switch(type) {
- case PendingIntentConstants.SMS_NOTIFICATION_ID:
- if (bundledNotification) {
- tag = buildNotificationTag(SMS_NOTIFICATION_TAG, conversationId);
- } else {
- tag = buildNotificationTag(SMS_NOTIFICATION_TAG, null);
- }
- break;
- case PendingIntentConstants.MSG_SEND_ERROR:
- tag = buildNotificationTag(SMS_ERROR_NOTIFICATION_TAG, null);
- break;
- }
- return tag;
- }
-
- private static void checkInitialized() {
- if (!sInitialized) {
- final Resources resources = Factory.get().getApplicationContext().getResources();
- sWearableImageWidth = resources.getDimensionPixelSize(
- R.dimen.notification_wearable_image_width);
- sWearableImageHeight = resources.getDimensionPixelSize(
- R.dimen.notification_wearable_image_height);
- sIconHeight = (int) resources.getDimension(
- android.R.dimen.notification_large_icon_height);
- sIconWidth =
- (int) resources.getDimension(android.R.dimen.notification_large_icon_width);
-
- sInitialized = true;
- }
- }
-
- private static void processAndSend(final NotificationState state, final boolean silent,
- final boolean softSound) {
- final Context context = Factory.get().getApplicationContext();
- final NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context);
- notifBuilder.setCategory(Notification.CATEGORY_MESSAGE);
- // TODO: Need to fix this for multi conversation notifications to rate limit dings.
- final String conversationId = state.mConversationIds.first();
-
-
- final Uri ringtoneUri = RingtoneUtil.getNotificationRingtoneUri(state.getRingtoneUri());
- // If the notification's conversation is currently observable (focused or in the
- // conversation list), then play a notification beep at a low volume and don't display an
- // actual notification.
- if (softSound) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "processAndSend: fromConversationId == " +
- "sCurrentlyDisplayedConversationId so NOT showing notification," +
- " but playing soft sound. conversationId: " + conversationId);
- }
- playObservableConversationNotificationSound(ringtoneUri);
- return;
- }
- state.mBaseRequestCode = state.mType;
-
- // Set the delete intent (except for bundled wearable notifications, which are dismissed
- // as a group, either from the wearable or when the summary notification is dismissed from
- // the host device).
- if (!(state instanceof BundledMessageNotificationState)) {
- final PendingIntent clearIntent = state.getClearIntent();
- notifBuilder.setDeleteIntent(clearIntent);
- }
-
- updateBuilderAudioVibrate(state, notifBuilder, silent, ringtoneUri, conversationId);
-
- // Set the content intent
- PendingIntent destinationIntent;
- if (state.mConversationIds.size() > 1) {
- // We have notifications for multiple conversation, go to the conversation list.
- destinationIntent = UIIntents.get()
- .getPendingIntentForConversationListActivity(context);
- } else {
- // We have a single conversation, go directly to that conversation.
- destinationIntent = UIIntents.get()
- .getPendingIntentForConversationActivity(context,
- state.mConversationIds.first(),
- null /*draft*/);
- }
- notifBuilder.setContentIntent(destinationIntent);
-
- // TODO: set based on contact coming from a favorite.
- notifBuilder.setPriority(state.getPriority());
-
- // Save the state of the notification in-progress so when the avatar is loaded,
- // we can continue building the notification.
- final NotificationCompat.Style notifStyle = state.build(notifBuilder);
- state.mNotificationBuilder = notifBuilder;
- state.mNotificationStyle = notifStyle;
- if (!state.mPeople.isEmpty()) {
- final Bundle people = new Bundle();
- people.putStringArray(NotificationCompat.EXTRA_PEOPLE,
- state.mPeople.toArray(new String[state.mPeople.size()]));
- notifBuilder.addExtras(people);
- }
-
- if (state.mParticipantAvatarsUris != null) {
- final Uri avatarUri = state.mParticipantAvatarsUris.get(0);
- final AvatarRequestDescriptor descriptor = new AvatarRequestDescriptor(avatarUri,
- sIconWidth, sIconHeight, OsUtil.isAtLeastL());
- final MediaRequest<ImageResource> imageRequest = descriptor.buildSyncMediaRequest(
- context);
-
- synchronized (sPendingNotifications) {
- sPendingNotifications.add(state);
- }
-
- // Synchronously load the avatar.
- final ImageResource avatarImage =
- MediaResourceManager.get().requestMediaResourceSync(imageRequest);
- if (avatarImage != null) {
- ImageResource avatarHiRes = null;
- try {
- if (isWearCompanionAppInstalled()) {
- // For Wear users, we need to request a high-res avatar image to use as the
- // notification card background. If the sender has a contact photo, we'll
- // request the display photo from the Contacts provider. Otherwise, we ask
- // the local content provider for a hi-res version of the generic avatar
- // (e.g. letter with colored background).
- avatarHiRes = requestContactDisplayPhoto(context,
- getDisplayPhotoUri(avatarUri));
- if (avatarHiRes == null) {
- final AvatarRequestDescriptor hiResDesc =
- new AvatarRequestDescriptor(avatarUri,
- sWearableImageWidth,
- sWearableImageHeight,
- false /* cropToCircle */,
- true /* isWearBackground */);
- avatarHiRes = MediaResourceManager.get().requestMediaResourceSync(
- hiResDesc.buildSyncMediaRequest(context));
- }
- }
-
- // We have to make copies of the bitmaps to hand to the NotificationManager
- // because the bitmap in the ImageResource is managed and will automatically
- // get released.
- Bitmap avatarBitmap = Bitmap.createBitmap(avatarImage.getBitmap());
- Bitmap avatarHiResBitmap = (avatarHiRes != null) ?
- Bitmap.createBitmap(avatarHiRes.getBitmap()) : null;
- sendNotification(state, avatarBitmap, avatarHiResBitmap);
- return;
- } finally {
- avatarImage.release();
- if (avatarHiRes != null) {
- avatarHiRes.release();
- }
- }
- }
- }
- // We have no avatar. Post the notification anyway.
- sendNotification(state, null, null);
- }
-
- /**
- * Returns the thumbnailUri from the avatar URI, or null if avatar URI does not have thumbnail.
- */
- private static Uri getThumbnailUri(final Uri avatarUri) {
- Uri localUri = null;
- final String avatarType = AvatarUriUtil.getAvatarType(avatarUri);
- if (TextUtils.equals(avatarType, AvatarUriUtil.TYPE_LOCAL_RESOURCE_URI)) {
- localUri = AvatarUriUtil.getPrimaryUri(avatarUri);
- } else if (UriUtil.isLocalResourceUri(avatarUri)) {
- localUri = avatarUri;
- }
- if (localUri != null && localUri.getAuthority().equals(ContactsContract.AUTHORITY)) {
- // Contact photos are of the form: content://com.android.contacts/contacts/123/photo
- final List<String> pathParts = localUri.getPathSegments();
- if (pathParts.size() == 3 &&
- pathParts.get(2).equals(Contacts.Photo.CONTENT_DIRECTORY)) {
- return localUri;
- }
- }
- return null;
- }
-
- /**
- * Returns the displayPhotoUri from the avatar URI, or null if avatar URI
- * does not have a displayPhotoUri.
- */
- private static Uri getDisplayPhotoUri(final Uri avatarUri) {
- final Uri thumbnailUri = getThumbnailUri(avatarUri);
- if (thumbnailUri == null) {
- return null;
- }
- final List<String> originalPaths = thumbnailUri.getPathSegments();
- final int originalPathsSize = originalPaths.size();
- final StringBuilder newPathBuilder = new StringBuilder();
- // Change content://com.android.contacts/contacts("_corp")/123/photo to
- // content://com.android.contacts/contacts("_corp")/123/display_photo
- for (int i = 0; i < originalPathsSize; i++) {
- newPathBuilder.append('/');
- if (i == 2) {
- newPathBuilder.append(ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
- } else {
- newPathBuilder.append(originalPaths.get(i));
- }
- }
- return thumbnailUri.buildUpon().path(newPathBuilder.toString()).build();
- }
-
- private static ImageResource requestContactDisplayPhoto(final Context context,
- final Uri displayPhotoUri) {
- final UriImageRequestDescriptor bgDescriptor =
- new UriImageRequestDescriptor(displayPhotoUri,
- sWearableImageWidth,
- sWearableImageHeight,
- false, /* allowCompression */
- true, /* isStatic */
- false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- return MediaResourceManager.get().requestMediaResourceSync(
- bgDescriptor.buildSyncMediaRequest(context));
- }
-
- private static void createMessageNotification(final boolean silent,
- final String conversationId) {
- final NotificationState state = MessageNotificationState.getNotificationState();
- final boolean softSound = DataModel.get().isNewMessageObservable(conversationId);
- if (state == null) {
- cancel(PendingIntentConstants.SMS_NOTIFICATION_ID);
- if (softSound && !TextUtils.isEmpty(conversationId)) {
- final Uri ringtoneUri = getNotificationRingtoneUriForConversationId(conversationId);
- playObservableConversationNotificationSound(ringtoneUri);
- }
- return;
- }
- processAndSend(state, silent, softSound);
-
- // The rest of the logic here is for supporting Android Wear devices, specifically for when
- // we are notifying about multiple conversations. In that case, the Inbox-style summary
- // notification (which we already processed above) appears on the phone (as it always has),
- // but wearables show per-conversation notifications, bundled together in a group.
-
- // It is valid to replace a notification group with another group with fewer conversations,
- // or even with one notification for a single conversation. In either case, we need to
- // explicitly cancel any children from the old group which are not being notified about now.
- final Context context = Factory.get().getApplicationContext();
- final ConversationIdSet oldGroupChildIds = getGroupChildIds(context);
- if (oldGroupChildIds != null && oldGroupChildIds.size() > 0) {
- cancelStaleGroupChildren(oldGroupChildIds, state);
- }
-
- // Send per-conversation notifications (if there are multiple conversations).
- final ConversationIdSet groupChildIds = new ConversationIdSet();
- if (state instanceof MultiConversationNotificationState) {
- for (final NotificationState child :
- ((MultiConversationNotificationState) state).mChildren) {
- processAndSend(child, true /* silent */, softSound);
- if (child.mConversationIds != null) {
- groupChildIds.add(child.mConversationIds.first());
- }
- }
- }
-
- // Record the new set of group children.
- writeGroupChildIds(context, groupChildIds);
- }
-
- private static void updateBuilderAudioVibrate(final NotificationState state,
- final NotificationCompat.Builder notifBuilder, final boolean silent,
- final Uri ringtoneUri, final String conversationId) {
- int defaults = Notification.DEFAULT_LIGHTS;
- if (!silent) {
- final BuglePrefs prefs = Factory.get().getApplicationPrefs();
- final long latestNotificationTimestamp = prefs.getLong(
- BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP, Long.MIN_VALUE);
- final long latestReceivedTimestamp = state.getLatestReceivedTimestamp();
- prefs.putLong(
- BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP,
- Math.max(latestNotificationTimestamp, latestReceivedTimestamp));
- if (latestReceivedTimestamp > latestNotificationTimestamp) {
- synchronized (mLock) {
- // Find out the last time we dinged for this conversation
- Long lastTime = sLastMessageDingTime.get(conversationId);
- if (sTimeBetweenDingsMs == 0) {
- sTimeBetweenDingsMs = BugleGservices.get().getInt(
- BugleGservicesKeys.NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS,
- BugleGservicesKeys.NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS_DEFAULT) *
- 1000;
- }
- if (lastTime == null
- || SystemClock.elapsedRealtime() - lastTime > sTimeBetweenDingsMs) {
- sLastMessageDingTime.put(conversationId, SystemClock.elapsedRealtime());
- notifBuilder.setSound(ringtoneUri);
- if (shouldVibrate(state)) {
- defaults |= Notification.DEFAULT_VIBRATE;
- }
- }
- }
- }
- }
- notifBuilder.setDefaults(defaults);
- }
-
- // TODO: this doesn't seem to be defined in NotificationCompat yet. Temporarily
- // define it here until it makes its way from Notification -> NotificationCompat.
- /**
- * Notification category: incoming direct message (SMS, instant message, etc.).
- */
- private static final String CATEGORY_MESSAGE = "msg";
-
- private static void sendNotification(final NotificationState notificationState,
- final Bitmap avatarIcon, final Bitmap avatarHiRes) {
- final Context context = Factory.get().getApplicationContext();
- if (notificationState.mCanceled) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "sendNotification: Notification already cancelled; dropping it");
- }
- return;
- }
-
- synchronized (sPendingNotifications) {
- if (sPendingNotifications.contains(notificationState)) {
- sPendingNotifications.remove(notificationState);
- }
- }
-
- notificationState.mNotificationBuilder
- .setSmallIcon(notificationState.getIcon())
- .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
- .setColor(context.getResources().getColor(R.color.notification_accent_color))
-// .setPublicVersion(null) // TODO: when/if we ever support different
- // text on the lockscreen, instead of "contents hidden"
- .setCategory(CATEGORY_MESSAGE);
-
- if (avatarIcon != null) {
- notificationState.mNotificationBuilder.setLargeIcon(avatarIcon);
- }
-
- if (notificationState.mParticipantContactUris != null &&
- notificationState.mParticipantContactUris.size() > 0) {
- for (final Uri contactUri : notificationState.mParticipantContactUris) {
- notificationState.mNotificationBuilder.addPerson(contactUri.toString());
- }
- }
-
- final Uri attachmentUri = notificationState.getAttachmentUri();
- final String attachmentType = notificationState.getAttachmentType();
- Bitmap attachmentBitmap = null;
-
- // For messages with photo/video attachment, request an image to show in the notification.
- if (attachmentUri != null && notificationState.mNotificationStyle != null &&
- (notificationState.mNotificationStyle instanceof
- NotificationCompat.BigPictureStyle) &&
- (ContentType.isImageType(attachmentType) ||
- ContentType.isVideoType(attachmentType))) {
- final boolean isVideo = ContentType.isVideoType(attachmentType);
-
- MediaRequest<ImageResource> imageRequest;
- if (isVideo) {
- Assert.isTrue(VideoThumbnailRequest.shouldShowIncomingVideoThumbnails());
- final MessagePartVideoThumbnailRequestDescriptor videoDescriptor =
- new MessagePartVideoThumbnailRequestDescriptor(attachmentUri);
- imageRequest = videoDescriptor.buildSyncMediaRequest(context);
- } else {
- final UriImageRequestDescriptor imageDescriptor =
- new UriImageRequestDescriptor(attachmentUri,
- sWearableImageWidth,
- sWearableImageHeight,
- false /* allowCompression */,
- true /* isStatic */,
- false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- imageRequest = imageDescriptor.buildSyncMediaRequest(context);
- }
- final ImageResource imageResource =
- MediaResourceManager.get().requestMediaResourceSync(imageRequest);
- if (imageResource != null) {
- try {
- // Copy the bitmap, because the one in the ImageResource is managed by
- // MediaResourceManager.
- Bitmap imageResourceBitmap = imageResource.getBitmap();
- Config config = imageResourceBitmap.getConfig();
-
- // Make sure our bitmap has a valid format.
- if (config == null) {
- config = Bitmap.Config.ARGB_8888;
- }
- attachmentBitmap = imageResourceBitmap.copy(config, true);
- } finally {
- imageResource.release();
- }
- }
- }
-
- fireOffNotification(notificationState, attachmentBitmap, avatarIcon, avatarHiRes);
- }
-
- private static void fireOffNotification(final NotificationState notificationState,
- final Bitmap attachmentBitmap, final Bitmap avatarBitmap, Bitmap avatarHiResBitmap) {
- if (notificationState.mCanceled) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Firing off notification, but notification already canceled");
- }
- return;
- }
-
- final Context context = Factory.get().getApplicationContext();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "MMS picture loaded, bitmap: " + attachmentBitmap);
- }
-
- final NotificationCompat.Builder notifBuilder = notificationState.mNotificationBuilder;
- notifBuilder.setStyle(notificationState.mNotificationStyle);
- notifBuilder.setColor(context.getResources().getColor(R.color.notification_accent_color));
-
- final WearableExtender wearableExtender = new WearableExtender();
- setWearableGroupOptions(notifBuilder, notificationState);
-
- if (avatarHiResBitmap != null) {
- wearableExtender.setBackground(avatarHiResBitmap);
- } else if (avatarBitmap != null) {
- // Nothing to do here; we already set avatarBitmap as the notification icon
- } else {
- final Bitmap defaultBackground = BitmapFactory.decodeResource(
- context.getResources(), R.drawable.bg_sms);
- wearableExtender.setBackground(defaultBackground);
- }
-
- if (notificationState instanceof MultiMessageNotificationState) {
- if (attachmentBitmap != null) {
- // When we've got a picture attachment, we do some switcheroo trickery. When
- // the notification is expanded, we show the picture as a bigPicture. The small
- // icon shows the sender's avatar. When that same notification is collapsed, the
- // picture is shown in the location where the avatar is normally shown. The lines
- // below make all that happen.
-
- // Here we're taking the picture attachment and making a small, scaled, center
- // cropped version of the picture we can stuff into the place where the avatar
- // goes when the notification is collapsed.
- final Bitmap smallBitmap = ImageUtils.scaleCenterCrop(attachmentBitmap, sIconWidth,
- sIconHeight);
- ((NotificationCompat.BigPictureStyle) notificationState.mNotificationStyle)
- .bigPicture(attachmentBitmap)
- .bigLargeIcon(avatarBitmap);
- notificationState.mNotificationBuilder.setLargeIcon(smallBitmap);
-
- // Add a wearable page with no visible card so you can more easily see the photo.
- final NotificationCompat.Builder photoPageNotifBuilder =
- new NotificationCompat.Builder(Factory.get().getApplicationContext());
- final WearableExtender photoPageWearableExtender = new WearableExtender();
- photoPageWearableExtender.setHintShowBackgroundOnly(true);
- if (attachmentBitmap != null) {
- final Bitmap wearBitmap = ImageUtils.scaleCenterCrop(attachmentBitmap,
- sWearableImageWidth, sWearableImageHeight);
- photoPageWearableExtender.setBackground(wearBitmap);
- }
- photoPageNotifBuilder.extend(photoPageWearableExtender);
- wearableExtender.addPage(photoPageNotifBuilder.build());
- }
-
- maybeAddWearableConversationLog(wearableExtender,
- (MultiMessageNotificationState) notificationState);
- addDownloadMmsAction(notifBuilder, wearableExtender, notificationState);
- addWearableVoiceReplyAction(wearableExtender, notificationState);
- }
-
- // Apply the wearable options and build & post the notification
- notifBuilder.extend(wearableExtender);
- doNotify(notifBuilder.build(), notificationState);
- }
-
- private static void setWearableGroupOptions(final NotificationCompat.Builder notifBuilder,
- final NotificationState notificationState) {
- final String groupKey = "groupkey";
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Group key (for wearables)=" + groupKey);
- }
- if (notificationState instanceof MultiConversationNotificationState) {
- notifBuilder.setGroup(groupKey).setGroupSummary(true);
- } else if (notificationState instanceof BundledMessageNotificationState) {
- final int order = ((BundledMessageNotificationState) notificationState).mGroupOrder;
- // Convert the order to a zero-padded string ("00", "01", "02", etc).
- // The Wear library orders notifications within a bundle lexicographically
- // by the sort key, hence the need for zeroes to preserve the ordering.
- final String sortKey = String.format(Locale.US, "%02d", order);
- notifBuilder.setGroup(groupKey).setSortKey(sortKey);
- }
- }
-
- private static void maybeAddWearableConversationLog(
- final WearableExtender wearableExtender,
- final MultiMessageNotificationState notificationState) {
- if (!isWearCompanionAppInstalled()) {
- return;
- }
- final String convId = notificationState.mConversationIds.first();
- ConversationLineInfo convInfo = notificationState.mConvList.mConvInfos.get(0);
- final Notification page = MessageNotificationState.buildConversationPageForWearable(
- convId,
- convInfo.mParticipantCount);
- if (page != null) {
- wearableExtender.addPage(page);
- }
- }
-
- private static void addWearableVoiceReplyAction(
- final WearableExtender wearableExtender, final NotificationState notificationState) {
- if (!(notificationState instanceof MultiMessageNotificationState)) {
- return;
- }
- final MultiMessageNotificationState multiMessageNotificationState =
- (MultiMessageNotificationState) notificationState;
- final Context context = Factory.get().getApplicationContext();
-
- final String conversationId = notificationState.mConversationIds.first();
- final ConversationLineInfo convInfo =
- multiMessageNotificationState.mConvList.mConvInfos.get(0);
- final String selfId = convInfo.mSelfParticipantId;
-
- final boolean requiresMms =
- MmsSmsUtils.getRequireMmsForEmailAddress(
- convInfo.mIncludeEmailAddress, convInfo.mSubId) ||
- (convInfo.mIsGroup && MmsUtils.groupMmsEnabled(convInfo.mSubId));
-
- final int requestCode = multiMessageNotificationState.getReplyIntentRequestCode();
- final PendingIntent replyPendingIntent = UIIntents.get()
- .getPendingIntentForSendingMessageToConversation(context,
- conversationId, selfId, requiresMms, requestCode);
-
- final int replyLabelRes = requiresMms ? R.string.notification_reply_via_mms :
- R.string.notification_reply_via_sms;
-
- final NotificationCompat.Action.Builder actionBuilder =
- new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
- context.getString(replyLabelRes), replyPendingIntent);
- final String[] choices = context.getResources().getStringArray(
- R.array.notification_reply_choices);
- final RemoteInput remoteInput = new RemoteInput.Builder(Intent.EXTRA_TEXT).setLabel(
- context.getString(R.string.notification_reply_prompt)).
- setChoices(choices)
- .build();
- actionBuilder.addRemoteInput(remoteInput);
- wearableExtender.addAction(actionBuilder.build());
- }
-
- private static void addDownloadMmsAction(final NotificationCompat.Builder notifBuilder,
- final WearableExtender wearableExtender, final NotificationState notificationState) {
- if (!(notificationState instanceof MultiMessageNotificationState)) {
- return;
- }
- final MultiMessageNotificationState multiMessageNotificationState =
- (MultiMessageNotificationState) notificationState;
- final ConversationLineInfo convInfo =
- multiMessageNotificationState.mConvList.mConvInfos.get(0);
- if (!convInfo.getDoesLatestMessageNeedDownload()) {
- return;
- }
- final String messageId = convInfo.getLatestMessageId();
- if (messageId == null) {
- // No message Id, no download for you
- return;
- }
- final Context context = Factory.get().getApplicationContext();
- final PendingIntent downloadPendingIntent =
- RedownloadMmsAction.getPendingIntentForRedownloadMms(context, messageId);
-
- final NotificationCompat.Action.Builder actionBuilder =
- new NotificationCompat.Action.Builder(R.drawable.ic_file_download_light,
- context.getString(R.string.notification_download_mms),
- downloadPendingIntent);
- final NotificationCompat.Action downloadAction = actionBuilder.build();
- notifBuilder.addAction(downloadAction);
-
- // Support the action on a wearable device as well
- wearableExtender.addAction(downloadAction);
- }
-
- private static synchronized void doNotify(final Notification notification,
- final NotificationState notificationState) {
- if (notification == null) {
- return;
- }
- final int type = notificationState.mType;
- final ConversationIdSet conversationIds = notificationState.mConversationIds;
- final boolean isBundledNotification =
- (notificationState instanceof BundledMessageNotificationState);
-
- // Mark the notification as finished
- notificationState.mCanceled = true;
-
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(Factory.get().getApplicationContext());
- // Only need conversationId for tags with a single conversation.
- String conversationId = null;
- if (conversationIds != null && conversationIds.size() == 1) {
- conversationId = conversationIds.first();
- }
- final String notificationTag = buildNotificationTag(type,
- conversationId, isBundledNotification);
-
- notification.flags |= Notification.FLAG_AUTO_CANCEL;
- notification.defaults |= Notification.DEFAULT_LIGHTS;
-
- notificationManager.notify(notificationTag, type, notification);
-
- LogUtil.i(TAG, "Notifying for conversation " + conversationId + "; "
- + "tag = " + notificationTag + ", type = " + type);
- }
-
- // This is the message string used in each line of an inboxStyle notification.
- // TODO: add attachment type
- static CharSequence formatInboxMessage(final String sender,
- final CharSequence message, final Uri attachmentUri, final String attachmentType) {
- final Context context = Factory.get().getApplicationContext();
- final TextAppearanceSpan notificationSenderSpan = new TextAppearanceSpan(
- context, R.style.NotificationSenderText);
-
- final TextAppearanceSpan notificationTertiaryText = new TextAppearanceSpan(
- context, R.style.NotificationTertiaryText);
-
- final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
- if (!TextUtils.isEmpty(sender)) {
- spannableStringBuilder.append(sender);
- spannableStringBuilder.setSpan(notificationSenderSpan, 0, sender.length(), 0);
- }
- final String separator = context.getString(R.string.notification_separator);
-
- if (!TextUtils.isEmpty(message)) {
- if (spannableStringBuilder.length() > 0) {
- spannableStringBuilder.append(separator);
- }
- final int start = spannableStringBuilder.length();
- spannableStringBuilder.append(message);
- spannableStringBuilder.setSpan(notificationTertiaryText, start,
- start + message.length(), 0);
- }
- if (attachmentUri != null) {
- if (spannableStringBuilder.length() > 0) {
- spannableStringBuilder.append(separator);
- }
- spannableStringBuilder.append(formatAttachmentTag(null, attachmentType));
- }
- return spannableStringBuilder;
- }
-
- protected static CharSequence buildColonSeparatedMessage(
- final String title, final CharSequence content, final Uri attachmentUri,
- final String attachmentType) {
- return buildBoldedMessage(title, content, attachmentUri, attachmentType,
- R.string.notification_ticker_separator);
- }
-
- protected static CharSequence buildSpaceSeparatedMessage(
- final String title, final CharSequence content, final Uri attachmentUri,
- final String attachmentType) {
- return buildBoldedMessage(title, content, attachmentUri, attachmentType,
- R.string.notification_space_separator);
- }
-
- /**
- * buildBoldedMessage - build a formatted message where the title is bold, there's a
- * separator, then the message.
- */
- private static CharSequence buildBoldedMessage(
- final String title, final CharSequence message, final Uri attachmentUri,
- final String attachmentType,
- final int separatorId) {
- final Context context = Factory.get().getApplicationContext();
- final SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
-
- // Boldify the title (which is the sender's name)
- if (!TextUtils.isEmpty(title)) {
- spanBuilder.append(title);
- spanBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, title.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- if (!TextUtils.isEmpty(message)) {
- if (spanBuilder.length() > 0) {
- spanBuilder.append(context.getString(separatorId));
- }
- spanBuilder.append(message);
- }
- if (attachmentUri != null) {
- if (spanBuilder.length() > 0) {
- final String separator = context.getString(R.string.notification_separator);
- spanBuilder.append(separator);
- }
- spanBuilder.append(formatAttachmentTag(null, attachmentType));
- }
- return spanBuilder;
- }
-
- static CharSequence formatAttachmentTag(final String author, final String attachmentType) {
- final Context context = Factory.get().getApplicationContext();
- final TextAppearanceSpan notificationSecondaryText = new TextAppearanceSpan(
- context, R.style.NotificationSecondaryText);
- final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
- if (!TextUtils.isEmpty(author)) {
- final TextAppearanceSpan notificationSenderSpan = new TextAppearanceSpan(
- context, R.style.NotificationSenderText);
- spannableStringBuilder.append(author);
- spannableStringBuilder.setSpan(notificationSenderSpan, 0, author.length(), 0);
- final String separator = context.getString(R.string.notification_separator);
- spannableStringBuilder.append(separator);
- }
- final int start = spannableStringBuilder.length();
- // The default attachment type is an image, since that's what was originally
- // supported. When there's no content type, assume it's an image.
- int message = R.string.notification_picture;
- if (ContentType.isAudioType(attachmentType)) {
- message = R.string.notification_audio;
- } else if (ContentType.isVideoType(attachmentType)) {
- message = R.string.notification_video;
- } else if (ContentType.isVCardType(attachmentType)) {
- message = R.string.notification_vcard;
- }
- spannableStringBuilder.append(context.getText(message));
- spannableStringBuilder.setSpan(notificationSecondaryText, start,
- spannableStringBuilder.length(), 0);
- return spannableStringBuilder;
- }
-
- /**
- * Play the observable conversation notification sound (it's the regular notification sound, but
- * played at half-volume)
- */
- private static void playObservableConversationNotificationSound(final Uri ringtoneUri) {
- final Context context = Factory.get().getApplicationContext();
- final AudioManager audioManager = (AudioManager) context
- .getSystemService(Context.AUDIO_SERVICE);
- final boolean silenced =
- audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
- if (silenced) {
- return;
- }
-
- final NotificationPlayer player = new NotificationPlayer(LogUtil.BUGLE_TAG);
- player.play(ringtoneUri, false,
- AudioManager.STREAM_NOTIFICATION,
- OBSERVABLE_CONVERSATION_NOTIFICATION_VOLUME);
-
- // Stop the sound after five seconds to handle continuous ringtones
- ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- player.stop();
- }
- }, 5000);
- }
-
- public static boolean isWearCompanionAppInstalled() {
- boolean found = false;
- try {
- Factory.get().getApplicationContext().getPackageManager()
- .getPackageInfo(WEARABLE_COMPANION_APP_PACKAGE, 0);
- found = true;
- } catch (final NameNotFoundException e) {
- // Ignore; found is already false
- }
- return found;
- }
-
- /**
- * When we go to the conversation list, call this to mark all messages as seen. That means
- * we won't show a notification again for the same message.
- */
- public static void markAllMessagesAsSeen() {
- MarkAsSeenAction.markAllAsSeen();
- resetLastMessageDing(null); // reset the ding timeout for all conversations
- }
-
- /**
- * When we open a particular conversation, call this to mark all messages as read.
- */
- public static void markMessagesAsRead(final String conversationId) {
- MarkAsReadAction.markAsRead(conversationId);
- resetLastMessageDing(conversationId);
- }
-
- /**
- * Returns the conversation ids of all active, grouped notifications, or
- * {code null} if no notifications are currently active and grouped.
- */
- private static ConversationIdSet getGroupChildIds(final Context context) {
- final String prefKey = context.getString(R.string.notifications_group_children_key);
- final String groupChildIdsText = BuglePrefs.getApplicationPrefs().getString(prefKey, "");
- if (!TextUtils.isEmpty(groupChildIdsText)) {
- return ConversationIdSet.createSet(groupChildIdsText);
- } else {
- return null;
- }
- }
-
- /**
- * Records the conversation ids of the currently active grouped notifications.
- */
- private static void writeGroupChildIds(final Context context,
- final ConversationIdSet childIds) {
- final ConversationIdSet oldChildIds = getGroupChildIds(context);
- if (childIds.equals(oldChildIds)) {
- return;
- }
- final String prefKey = context.getString(R.string.notifications_group_children_key);
- BuglePrefs.getApplicationPrefs().putString(prefKey, childIds.getDelimitedString());
- }
-
- /**
- * Reset the timer for a notification ding on a particular conversation or all conversations.
- */
- public static void resetLastMessageDing(final String conversationId) {
- synchronized (mLock) {
- if (TextUtils.isEmpty(conversationId)) {
- // reset all conversation dings
- sLastMessageDingTime.clear();
- } else {
- sLastMessageDingTime.remove(conversationId);
- }
- }
- }
-
- public static void notifyEmergencySmsFailed(final String emergencyNumber,
- final String conversationId) {
- final Context context = Factory.get().getApplicationContext();
-
- final CharSequence line1 = MessageNotificationState.applyWarningTextColor(context,
- context.getString(R.string.notification_emergency_send_failure_line1,
- emergencyNumber));
- final String line2 = context.getString(R.string.notification_emergency_send_failure_line2,
- emergencyNumber);
- final PendingIntent destinationIntent = UIIntents.get()
- .getPendingIntentForConversationActivity(context, conversationId, null /* draft */);
-
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
- builder.setTicker(line1)
- .setContentTitle(line1)
- .setContentText(line2)
- .setStyle(new NotificationCompat.BigTextStyle(builder).bigText(line2))
- .setSmallIcon(R.drawable.ic_failed_light)
- .setContentIntent(destinationIntent)
- .setSound(UriUtil.getUriForResourceId(context, R.raw.message_failure));
-
- final String tag = context.getPackageName() + ":emergency_sms_error";
- NotificationManagerCompat.from(context).notify(
- tag,
- PendingIntentConstants.MSG_SEND_ERROR,
- builder.build());
- }
-}
-
diff --git a/src/com/android/messaging/datamodel/BugleRecipientEntry.java b/src/com/android/messaging/datamodel/BugleRecipientEntry.java
deleted file mode 100644
index 2a9e5ff..0000000
--- a/src/com/android/messaging/datamodel/BugleRecipientEntry.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel;
-
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.ex.chips.RecipientEntry;
-
-/**
- * An extension of RecipientEntry for Bugle's use since Bugle uses phone numbers to identify
- * participants / recipients instead of contact ids. This allows the user to send to multiple
- * phone numbers of the same contact.
- */
-public class BugleRecipientEntry extends RecipientEntry {
-
- protected BugleRecipientEntry(final int entryType, final String displayName,
- final String destination, final int destinationType, final String destinationLabel,
- final long contactId, final Long directoryId, final long dataId,
- final Uri photoThumbnailUri, final boolean isFirstLevel, final boolean isValid,
- final String lookupKey) {
- super(entryType, displayName, destination, destinationType, destinationLabel, contactId,
- directoryId, dataId, photoThumbnailUri, isFirstLevel, isValid, lookupKey);
- }
-
- public static BugleRecipientEntry constructTopLevelEntry(final String displayName,
- final int displayNameSource, final String destination, final int destinationType,
- final String destinationLabel, final long contactId, final Long directoryId,
- final long dataId, final String thumbnailUriAsString, final boolean isValid,
- final String lookupKey) {
- return new BugleRecipientEntry(ENTRY_TYPE_PERSON, displayName, destination, destinationType,
- destinationLabel, contactId, directoryId, dataId, (thumbnailUriAsString != null
- ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey);
- }
-
- public static BugleRecipientEntry constructSecondLevelEntry(final String displayName,
- final int displayNameSource, final String destination, final int destinationType,
- final String destinationLabel, final long contactId, final Long directoryId,
- final long dataId, final String thumbnailUriAsString, final boolean isValid,
- final String lookupKey) {
- return new BugleRecipientEntry(ENTRY_TYPE_PERSON, displayName, destination, destinationType,
- destinationLabel, contactId, directoryId, dataId, (thumbnailUriAsString != null
- ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey);
- }
-
- @Override
- public boolean isSamePerson(final RecipientEntry entry) {
- return getDestination() != null && entry.getDestination() != null &&
- TextUtils.equals(getDestination(), entry.getDestination());
- }
-}
diff --git a/src/com/android/messaging/datamodel/ConversationImagePartsView.java b/src/com/android/messaging/datamodel/ConversationImagePartsView.java
deleted file mode 100644
index 70ba381..0000000
--- a/src/com/android/messaging/datamodel/ConversationImagePartsView.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.provider.BaseColumns;
-
-import com.android.ex.photo.provider.PhotoContract.PhotoViewColumns;
-
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.util.ContentType;
-
-/**
- * View for the image parts for the conversation. It is used to provide the photoviewer with a
- * a data source for all the photos in a conversation, so that the photoviewer can support paging
- * through all the photos of the conversation. The columns of the view are a superset of
- * {@link com.android.ex.photo.provider.PhotoContract.PhotoViewColumns}.
- */
-public class ConversationImagePartsView {
- private static final String VIEW_NAME = "conversation_image_parts_view";
-
- private static final String CREATE_SQL = "CREATE VIEW " +
- VIEW_NAME + " AS SELECT "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID
- + " as " + Columns.CONVERSATION_ID + ", "
- + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_URI
- + " as " + Columns.URI + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FULL_NAME
- + " as " + Columns.SENDER_FULL_NAME + ", "
- + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_URI
- + " as " + Columns.CONTENT_URI + ", "
- // Use NULL as the thumbnail uri
- + " NULL as " + Columns.THUMBNAIL_URI + ", "
- + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_TYPE
- + " as " + Columns.CONTENT_TYPE + ", "
- //
- // Columns in addition to those specified by PhotoContract
- //
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
- + " as " + Columns.DISPLAY_DESTINATION + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP
- + " as " + Columns.RECEIVED_TIMESTAMP + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
- + " as " + Columns.STATUS + " "
-
- + " FROM " + DatabaseHelper.MESSAGES_TABLE + " LEFT JOIN " + DatabaseHelper.PARTS_TABLE
- + " ON (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns._ID
- + "=" + DatabaseHelper.PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ") "
- + " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE + " ON ("
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
- + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns._ID + ")"
-
- // "content_type like 'image/%'"
- + " WHERE " + DatabaseHelper.PARTS_TABLE + "." + PartColumns.CONTENT_TYPE
- + " like '" + ContentType.IMAGE_PREFIX + "%'"
-
- + " ORDER BY "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " ASC, "
- + DatabaseHelper.PARTS_TABLE + '.' + PartColumns._ID + " ASC";
-
- static class Columns implements BaseColumns {
- static final String CONVERSATION_ID = MessageColumns.CONVERSATION_ID;
- static final String URI = PhotoViewColumns.URI;
- static final String SENDER_FULL_NAME = PhotoViewColumns.NAME;
- static final String CONTENT_URI = PhotoViewColumns.CONTENT_URI;
- static final String THUMBNAIL_URI = PhotoViewColumns.THUMBNAIL_URI;
- static final String CONTENT_TYPE = PhotoViewColumns.CONTENT_TYPE;
- // Columns in addition to those specified by PhotoContract
- static final String DISPLAY_DESTINATION = ParticipantColumns.DISPLAY_DESTINATION;
- static final String RECEIVED_TIMESTAMP = MessageColumns.RECEIVED_TIMESTAMP;
- static final String STATUS = MessageColumns.STATUS;
- }
-
- public interface PhotoViewQuery {
- public final String[] PROJECTION = {
- PhotoViewColumns.URI,
- PhotoViewColumns.NAME,
- PhotoViewColumns.CONTENT_URI,
- PhotoViewColumns.THUMBNAIL_URI,
- PhotoViewColumns.CONTENT_TYPE,
- // Columns in addition to those specified by PhotoContract
- Columns.DISPLAY_DESTINATION,
- Columns.RECEIVED_TIMESTAMP,
- Columns.STATUS,
- };
-
- public final int INDEX_URI = 0;
- public final int INDEX_SENDER_FULL_NAME = 1;
- public final int INDEX_CONTENT_URI = 2;
- public final int INDEX_THUMBNAIL_URI = 3;
- public final int INDEX_CONTENT_TYPE = 4;
- // Columns in addition to those specified by PhotoContract
- public final int INDEX_DISPLAY_DESTINATION = 5;
- public final int INDEX_RECEIVED_TIMESTAMP = 6;
- public final int INDEX_STATUS = 7;
- }
-
- static final String getViewName() {
- return VIEW_NAME;
- }
-
- static final String getCreateSql() {
- return CREATE_SQL;
- }
-}
diff --git a/src/com/android/messaging/datamodel/CursorQueryData.java b/src/com/android/messaging/datamodel/CursorQueryData.java
deleted file mode 100644
index 3e6a656..0000000
--- a/src/com/android/messaging/datamodel/CursorQueryData.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-import com.android.messaging.util.Assert;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Holds parameters and data (such as content URI) for performing queries on the content provider.
- * This class could then be used to perform a query using either a BoundCursorLoader or querying
- * on the content resolver directly.
- *
- * This class is used for cases where the way to load a cursor is not fixed. For example,
- * when using ContactUtil to query for phone numbers, the ContactPickerFragment wants to use
- * a CursorLoader to asynchronously load the data and tie in nicely with its data binding
- * paradigm, whereas ContactRecipientAdapter wants to synchronously perform the query on the
- * worker thread.
- */
-public class CursorQueryData {
- protected final Uri mUri;
- protected final String[] mProjection;
- protected final String mSelection;
- protected final String[] mSelectionArgs;
- protected final String mSortOrder;
- protected final Context mContext;
-
- public CursorQueryData(final Context context, final Uri uri, final String[] projection,
- final String selection, final String[] selectionArgs, final String sortOrder) {
- mContext = context;
- mUri = uri;
- mProjection = projection;
- mSelection = selection;
- mSelectionArgs = selectionArgs;
- mSortOrder = sortOrder;
- }
-
- public BoundCursorLoader createBoundCursorLoader(final String bindingId) {
- return new BoundCursorLoader(bindingId, mContext, mUri, mProjection, mSelection,
- mSelectionArgs, mSortOrder);
- }
-
- public Cursor performSynchronousQuery() {
- Assert.isNotMainThread();
- if (mUri == null) {
- // See {@link #getEmptyQueryData}
- return null;
- } else {
- return mContext.getContentResolver().query(mUri, mProjection, mSelection,
- mSelectionArgs, mSortOrder);
- }
- }
-
- @VisibleForTesting
- public Uri getUri() {
- return mUri;
- }
-
- /**
- * Representation of an invalid query. {@link #performSynchronousQuery} will return
- * a null Cursor.
- */
- public static CursorQueryData getEmptyQueryData() {
- return new CursorQueryData(null, null, null, null, null, null);
- }
-}
diff --git a/src/com/android/messaging/datamodel/DataModel.java b/src/com/android/messaging/datamodel/DataModel.java
deleted file mode 100644
index 936b51c..0000000
--- a/src/com/android/messaging/datamodel/DataModel.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.action.Action;
-import com.android.messaging.datamodel.action.ActionService;
-import com.android.messaging.datamodel.action.BackgroundWorker;
-import com.android.messaging.datamodel.data.BlockedParticipantsData;
-import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
-import com.android.messaging.datamodel.data.ContactListItemData;
-import com.android.messaging.datamodel.data.ContactPickerData;
-import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
-import com.android.messaging.datamodel.data.ConversationData;
-import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.datamodel.data.LaunchConversationData;
-import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener;
-import com.android.messaging.datamodel.data.MediaPickerData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.ParticipantListItemData;
-import com.android.messaging.datamodel.data.PeopleAndOptionsData;
-import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
-import com.android.messaging.datamodel.data.PeopleOptionsItemData;
-import com.android.messaging.datamodel.data.SettingsData;
-import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
-import com.android.messaging.datamodel.data.SubscriptionListData;
-import com.android.messaging.datamodel.data.VCardContactItemData;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.ConnectivityUtil;
-
-public abstract class DataModel {
- private String mFocusedConversation;
- private boolean mConversationListScrolledToNewestConversation;
-
- public static DataModel get() {
- return Factory.get().getDataModel();
- }
-
- public static final void startActionService(final Action action) {
- get().getActionService().startAction(action);
- }
-
- public static final void scheduleAction(final Action action,
- final int code, final long delayMs) {
- get().getActionService().scheduleAction(action, code, delayMs);
- }
-
- public abstract ConversationListData createConversationListData(final Context context,
- final ConversationListDataListener listener, final boolean archivedMode);
-
- public abstract ConversationData createConversationData(final Context context,
- final ConversationDataListener listener, final String conversationId);
-
- public abstract ContactListItemData createContactListItemData();
-
- public abstract ContactPickerData createContactPickerData(final Context context,
- final ContactPickerDataListener listener);
-
- public abstract MediaPickerData createMediaPickerData(final Context context);
-
- public abstract GalleryGridItemData createGalleryGridItemData();
-
- public abstract LaunchConversationData createLaunchConversationData(
- LaunchConversationDataListener listener);
-
- public abstract PeopleOptionsItemData createPeopleOptionsItemData(final Context context);
-
- public abstract PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
- final Context context, final PeopleAndOptionsDataListener listener);
-
- public abstract VCardContactItemData createVCardContactItemData(final Context context,
- final MessagePartData data);
-
- public abstract VCardContactItemData createVCardContactItemData(final Context context,
- final Uri vCardUri);
-
- public abstract ParticipantListItemData createParticipantListItemData(
- final ParticipantData participant);
-
- public abstract BlockedParticipantsData createBlockedParticipantsData(Context context,
- BlockedParticipantsDataListener listener);
-
- public abstract SubscriptionListData createSubscriptonListData(Context context);
-
- public abstract SettingsData createSettingsData(Context context, SettingsDataListener listener);
-
- public abstract DraftMessageData createDraftMessageData(String conversationId);
-
- public abstract ActionService getActionService();
-
- public abstract BackgroundWorker getBackgroundWorkerForActionService();
-
- @DoesNotRunOnMainThread
- public abstract DatabaseWrapper getDatabase();
-
- // Allow DataModel to coordinate with activity lifetime events.
- public abstract void onActivityResume();
-
- abstract void onCreateTables(final SQLiteDatabase db);
-
- public void setFocusedConversation(final String conversationId) {
- mFocusedConversation = conversationId;
- }
-
- public boolean isFocusedConversation(final String conversationId) {
- return !TextUtils.isEmpty(mFocusedConversation)
- && TextUtils.equals(mFocusedConversation, conversationId);
- }
-
- public void setConversationListScrolledToNewestConversation(
- final boolean scrolledToNewestConversation) {
- mConversationListScrolledToNewestConversation = scrolledToNewestConversation;
- }
-
- public boolean isConversationListScrolledToNewestConversation() {
- return mConversationListScrolledToNewestConversation;
- }
-
- /**
- * If a new message is received in the specified conversation, will the user be able to
- * observe it in some UI within the app?
- * @param conversationId conversation with the new incoming message
- */
- public boolean isNewMessageObservable(final String conversationId) {
- return isConversationListScrolledToNewestConversation()
- || isFocusedConversation(conversationId);
- }
-
- public abstract void onApplicationCreated();
-
- public abstract ConnectivityUtil getConnectivityUtil();
-
- public abstract SyncManager getSyncManager();
-}
diff --git a/src/com/android/messaging/datamodel/DataModelException.java b/src/com/android/messaging/datamodel/DataModelException.java
deleted file mode 100644
index 7084438..0000000
--- a/src/com/android/messaging/datamodel/DataModelException.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-public class DataModelException extends Exception {
- private static final long serialVersionUID = 1L;
-
- private static final int FIRST = 100;
-
- // ERRORS GENERATED INTERNALLY BY DATA MODEL.
-
- // ERRORS RELATED WITH SMS.
- public static final int ERROR_SMS_TEMPORARY_FAILURE = 116;
- public static final int ERROR_SMS_PERMANENT_FAILURE = 117;
- public static final int ERROR_MMS_TEMPORARY_FAILURE = 118;
- public static final int ERROR_MMS_PERMANENT_UNKNOWN_FAILURE = 119;
-
- // Request expired.
- public static final int ERROR_EXPIRED = 120;
- // Request canceled by user.
- public static final int ERROR_CANCELED = 121;
-
- public static final int ERROR_MOBILE_DATA_DISABLED = 123;
- public static final int ERROR_MMS_SERVICE_BLOCKED = 124;
- public static final int ERROR_MMS_INVALID_ADDRESS = 125;
- public static final int ERROR_MMS_NETWORK_PROBLEM = 126;
- public static final int ERROR_MMS_MESSAGE_NOT_FOUND = 127;
- public static final int ERROR_MMS_MESSAGE_FORMAT_CORRUPT = 128;
- public static final int ERROR_MMS_CONTENT_NOT_ACCEPTED = 129;
- public static final int ERROR_MMS_MESSAGE_NOT_SUPPORTED = 130;
- public static final int ERROR_MMS_REPLY_CHARGING_ERROR = 131;
- public static final int ERROR_MMS_ADDRESS_HIDING_NOT_SUPPORTED = 132;
- public static final int ERROR_MMS_LACK_OF_PREPAID = 133;
- public static final int ERROR_MMS_CAN_NOT_PERSIST = 134;
- public static final int ERROR_MMS_NO_AVAILABLE_APN = 135;
- public static final int ERROR_MMS_INVALID_MESSAGE_TO_SEND = 136;
- public static final int ERROR_MMS_INVALID_MESSAGE_RECEIVED = 137;
- public static final int ERROR_MMS_NO_CONFIGURATION = 138;
-
- private static final int LAST = 138;
-
- private final boolean mIsInjection;
- private final int mErrorCode;
- private final String mMessage;
- private final long mBackoff;
-
- public DataModelException(final int errorCode, final Exception innerException,
- final long backoff, final boolean injection, final String message) {
- // Since some of the exceptions passed in may not be serializable, only record message
- // instead of setting inner exception for Exception class. Otherwise, we will get
- // serialization issues when we pass ServerRequestException as intent extra later.
- if (errorCode < FIRST || errorCode > LAST) {
- throw new IllegalArgumentException("error code out of range: " + errorCode);
- }
- mIsInjection = injection;
- mErrorCode = errorCode;
- if (innerException != null) {
- mMessage = innerException.getMessage() + " -- " +
- (mIsInjection ? "[INJECTED] -- " : "") + message;
- } else {
- mMessage = (mIsInjection ? "[INJECTED] -- " : "") + message;
- }
-
- mBackoff = backoff;
- }
-
- public DataModelException(final int errorCode) {
- this(errorCode, null, 0, false, null);
- }
-
- public DataModelException(final int errorCode, final Exception innerException) {
- this(errorCode, innerException, 0, false, null);
- }
-
- public DataModelException(final int errorCode, final String message) {
- this(errorCode, null, 0, false, message);
- }
-
- @Override
- public String getMessage() {
- return mMessage;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-}
diff --git a/src/com/android/messaging/datamodel/DataModelImpl.java b/src/com/android/messaging/datamodel/DataModelImpl.java
deleted file mode 100644
index 6ab3f00..0000000
--- a/src/com/android/messaging/datamodel/DataModelImpl.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.telephony.SubscriptionManager;
-
-import com.android.messaging.datamodel.action.ActionService;
-import com.android.messaging.datamodel.action.BackgroundWorker;
-import com.android.messaging.datamodel.action.FixupMessageStatusOnStartupAction;
-import com.android.messaging.datamodel.action.ProcessPendingMessagesAction;
-import com.android.messaging.datamodel.data.BlockedParticipantsData;
-import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
-import com.android.messaging.datamodel.data.ContactListItemData;
-import com.android.messaging.datamodel.data.ContactPickerData;
-import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
-import com.android.messaging.datamodel.data.ConversationData;
-import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.datamodel.data.LaunchConversationData;
-import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener;
-import com.android.messaging.datamodel.data.MediaPickerData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.ParticipantListItemData;
-import com.android.messaging.datamodel.data.PeopleAndOptionsData;
-import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
-import com.android.messaging.datamodel.data.PeopleOptionsItemData;
-import com.android.messaging.datamodel.data.SettingsData;
-import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
-import com.android.messaging.datamodel.data.SubscriptionListData;
-import com.android.messaging.datamodel.data.VCardContactItemData;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.ConnectivityUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-public class DataModelImpl extends DataModel {
- private final Context mContext;
- private final ActionService mActionService;
- private final BackgroundWorker mDataModelWorker;
- private final DatabaseHelper mDatabaseHelper;
- private final ConnectivityUtil mConnectivityUtil;
- private final SyncManager mSyncManager;
-
- public DataModelImpl(final Context context) {
- super();
- mContext = context;
- mActionService = new ActionService();
- mDataModelWorker = new BackgroundWorker();
- mDatabaseHelper = DatabaseHelper.getInstance(context);
- mConnectivityUtil = new ConnectivityUtil(context);
- mSyncManager = new SyncManager();
- }
-
- @Override
- public ConversationListData createConversationListData(final Context context,
- final ConversationListDataListener listener, final boolean archivedMode) {
- return new ConversationListData(context, listener, archivedMode);
- }
-
- @Override
- public ConversationData createConversationData(final Context context,
- final ConversationDataListener listener, final String conversationId) {
- return new ConversationData(context, listener, conversationId);
- }
-
- @Override
- public ContactListItemData createContactListItemData() {
- return new ContactListItemData();
- }
-
- @Override
- public ContactPickerData createContactPickerData(final Context context,
- final ContactPickerDataListener listener) {
- return new ContactPickerData(context, listener);
- }
-
- @Override
- public BlockedParticipantsData createBlockedParticipantsData(
- final Context context, final BlockedParticipantsDataListener listener) {
- return new BlockedParticipantsData(context, listener);
- }
-
- @Override
- public MediaPickerData createMediaPickerData(final Context context) {
- return new MediaPickerData(context);
- }
-
- @Override
- public GalleryGridItemData createGalleryGridItemData() {
- return new GalleryGridItemData();
- }
-
- @Override
- public LaunchConversationData createLaunchConversationData(
- final LaunchConversationDataListener listener) {
- return new LaunchConversationData(listener);
- }
-
- @Override
- public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) {
- return new PeopleOptionsItemData(context);
- }
-
- @Override
- public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
- final Context context, final PeopleAndOptionsDataListener listener) {
- return new PeopleAndOptionsData(conversationId, context, listener);
- }
-
- @Override
- public VCardContactItemData createVCardContactItemData(final Context context,
- final MessagePartData data) {
- return new VCardContactItemData(context, data);
- }
-
- @Override
- public VCardContactItemData createVCardContactItemData(final Context context,
- final Uri vCardUri) {
- return new VCardContactItemData(context, vCardUri);
- }
-
- @Override
- public ParticipantListItemData createParticipantListItemData(
- final ParticipantData participant) {
- return new ParticipantListItemData(participant);
- }
-
- @Override
- public SubscriptionListData createSubscriptonListData(Context context) {
- return new SubscriptionListData(context);
- }
-
- @Override
- public SettingsData createSettingsData(Context context, SettingsDataListener listener) {
- return new SettingsData(context, listener);
- }
-
- @Override
- public DraftMessageData createDraftMessageData(String conversationId) {
- return new DraftMessageData(conversationId);
- }
-
- @Override
- public ActionService getActionService() {
- // We need to allow access to this on the UI thread since it's used to start actions.
- return mActionService;
- }
-
- @Override
- public BackgroundWorker getBackgroundWorkerForActionService() {
- return mDataModelWorker;
- }
-
- @Override
- @DoesNotRunOnMainThread
- public DatabaseWrapper getDatabase() {
- // We prevent the main UI thread from accessing the database since we have to allow
- // public access to this class to enable sub-packages to access data.
- Assert.isNotMainThread();
- return mDatabaseHelper.getDatabase();
- }
-
- @Override
- public ConnectivityUtil getConnectivityUtil() {
- return mConnectivityUtil;
- }
-
- @Override
- public SyncManager getSyncManager() {
- return mSyncManager;
- }
-
- @Override
- void onCreateTables(final SQLiteDatabase db) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Rebuilt databases: reseting related state");
- // Clear other things that implicitly reference the DB
- SyncManager.resetLastSyncTimestamps();
- }
-
- @Override
- public void onActivityResume() {
- // Perform an incremental sync and register for changes if necessary
- mSyncManager.updateSyncObserver(mContext);
-
- // Trigger a participant refresh if needed, we should only need to refresh if there is
- // contact change while the activity was paused.
- ParticipantRefresh.refreshParticipantsIfNeeded();
- }
-
- @Override
- public void onApplicationCreated() {
- FixupMessageStatusOnStartupAction.fixupMessageStatus();
- ProcessPendingMessagesAction.processFirstPendingMessage();
- SyncManager.immediateSync();
-
- if (OsUtil.isAtLeastL_MR1()) {
- // Start listening for subscription change events for refreshing self participants.
- PhoneUtils.getDefault().toLMr1().registerOnSubscriptionsChangedListener(
- new SubscriptionManager.OnSubscriptionsChangedListener() {
- @Override
- public void onSubscriptionsChanged() {
- // TODO: This dynamically changes the mms config that app is
- // currently using. It may cause inconsistency in some cases. We need
- // to check the usage of mms config and handle the dynamic change
- // gracefully
- MmsConfig.loadAsync();
- ParticipantRefresh.refreshSelfParticipants();
- }
- });
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/DatabaseHelper.java b/src/com/android/messaging/datamodel/DatabaseHelper.java
deleted file mode 100644
index f16bb3c..0000000
--- a/src/com/android/messaging/datamodel/DatabaseHelper.java
+++ /dev/null
@@ -1,813 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.provider.BaseColumns;
-
-import com.android.messaging.BugleApplication;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.LogUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * TODO: Open Issues:
- * - Should we be storing the draft messages in the regular messages table or should we have a
- * separate table for drafts to keep the normal messages query as simple as possible?
- */
-
-/**
- * Allows access to the SQL database. This is package private.
- */
-public class DatabaseHelper extends SQLiteOpenHelper {
- public static final String DATABASE_NAME = "bugle_db";
-
- private static final int getDatabaseVersion(final Context context) {
- return Integer.parseInt(context.getResources().getString(R.string.database_version));
- }
-
- /** Table containing names of all other tables and views */
- private static final String MASTER_TABLE = "sqlite_master";
- /** Column containing the name of the tables and views */
- private static final String[] MASTER_COLUMNS = new String[] { "name", };
-
- // Table names
- public static final String CONVERSATIONS_TABLE = "conversations";
- public static final String MESSAGES_TABLE = "messages";
- public static final String PARTS_TABLE = "parts";
- public static final String PARTICIPANTS_TABLE = "participants";
- public static final String CONVERSATION_PARTICIPANTS_TABLE = "conversation_participants";
-
- // Views
- static final String DRAFT_PARTS_VIEW = "draft_parts_view";
-
- // Conversations table schema
- public static class ConversationColumns implements BaseColumns {
- /* SMS/MMS Thread ID from the system provider */
- public static final String SMS_THREAD_ID = "sms_thread_id";
-
- /* Display name for the conversation */
- public static final String NAME = "name";
-
- /* Latest Message ID for the read status to display in conversation list */
- public static final String LATEST_MESSAGE_ID = "latest_message_id";
-
- /* Latest text snippet for display in conversation list */
- public static final String SNIPPET_TEXT = "snippet_text";
-
- /* Latest text subject for display in conversation list, empty string if none exists */
- public static final String SUBJECT_TEXT = "subject_text";
-
- /* Preview Uri */
- public static final String PREVIEW_URI = "preview_uri";
-
- /* The preview uri's content type */
- public static final String PREVIEW_CONTENT_TYPE = "preview_content_type";
-
- /* If we should display the current draft snippet/preview pair or snippet/preview pair */
- public static final String SHOW_DRAFT = "show_draft";
-
- /* Latest draft text subject for display in conversation list, empty string if none exists*/
- public static final String DRAFT_SUBJECT_TEXT = "draft_subject_text";
-
- /* Latest draft text snippet for display, empty string if none exists */
- public static final String DRAFT_SNIPPET_TEXT = "draft_snippet_text";
-
- /* Draft Preview Uri, empty string if none exists */
- public static final String DRAFT_PREVIEW_URI = "draft_preview_uri";
-
- /* The preview uri's content type */
- public static final String DRAFT_PREVIEW_CONTENT_TYPE = "draft_preview_content_type";
-
- /* If this conversation is archived */
- public static final String ARCHIVE_STATUS = "archive_status";
-
- /* Timestamp for sorting purposes */
- public static final String SORT_TIMESTAMP = "sort_timestamp";
-
- /* Last read message timestamp */
- public static final String LAST_READ_TIMESTAMP = "last_read_timestamp";
-
- /* Avatar for the conversation. Could be for group of individual */
- public static final String ICON = "icon";
-
- /* Participant contact ID if this conversation has a single participant. -1 otherwise */
- public static final String PARTICIPANT_CONTACT_ID = "participant_contact_id";
-
- /* Participant lookup key if this conversation has a single participant. null otherwise */
- public static final String PARTICIPANT_LOOKUP_KEY = "participant_lookup_key";
-
- /*
- * Participant's normalized destination if this conversation has a single participant.
- * null otherwise.
- */
- public static final String OTHER_PARTICIPANT_NORMALIZED_DESTINATION =
- "participant_normalized_destination";
-
- /* Default self participant for the conversation */
- public static final String CURRENT_SELF_ID = "current_self_id";
-
- /* Participant count not including self (so will be 1 for 1:1 or bigger for group) */
- public static final String PARTICIPANT_COUNT = "participant_count";
-
- /* Should notifications be enabled for this conversation? */
- public static final String NOTIFICATION_ENABLED = "notification_enabled";
-
- /* Notification sound used for the conversation */
- public static final String NOTIFICATION_SOUND_URI = "notification_sound_uri";
-
- /* Should vibrations be enabled for the conversation's notification? */
- public static final String NOTIFICATION_VIBRATION = "notification_vibration";
-
- /* Conversation recipients include email address */
- public static final String INCLUDE_EMAIL_ADDRESS = "include_email_addr";
-
- // Record the last received sms's service center info if it indicates that the reply path
- // is present (TP-Reply-Path), so that we could use it for the subsequent message to send.
- // Refer to TS 23.040 D.6 and SmsMessageSender.java in Android Messaging app.
- public static final String SMS_SERVICE_CENTER = "sms_service_center";
- }
-
- // Conversation table SQL
- private static final String CREATE_CONVERSATIONS_TABLE_SQL =
- "CREATE TABLE " + CONVERSATIONS_TABLE + "("
- + ConversationColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- // TODO : Int? Required not default?
- + ConversationColumns.SMS_THREAD_ID + " INT DEFAULT(0), "
- + ConversationColumns.NAME + " TEXT, "
- + ConversationColumns.LATEST_MESSAGE_ID + " INT, "
- + ConversationColumns.SNIPPET_TEXT + " TEXT, "
- + ConversationColumns.SUBJECT_TEXT + " TEXT, "
- + ConversationColumns.PREVIEW_URI + " TEXT, "
- + ConversationColumns.PREVIEW_CONTENT_TYPE + " TEXT, "
- + ConversationColumns.SHOW_DRAFT + " INT DEFAULT(0), "
- + ConversationColumns.DRAFT_SNIPPET_TEXT + " TEXT, "
- + ConversationColumns.DRAFT_SUBJECT_TEXT + " TEXT, "
- + ConversationColumns.DRAFT_PREVIEW_URI + " TEXT, "
- + ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE + " TEXT, "
- + ConversationColumns.ARCHIVE_STATUS + " INT DEFAULT(0), "
- + ConversationColumns.SORT_TIMESTAMP + " INT DEFAULT(0), "
- + ConversationColumns.LAST_READ_TIMESTAMP + " INT DEFAULT(0), "
- + ConversationColumns.ICON + " TEXT, "
- + ConversationColumns.PARTICIPANT_CONTACT_ID + " INT DEFAULT ( "
- + ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED + "), "
- + ConversationColumns.PARTICIPANT_LOOKUP_KEY + " TEXT, "
- + ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + " TEXT, "
- + ConversationColumns.CURRENT_SELF_ID + " TEXT, "
- + ConversationColumns.PARTICIPANT_COUNT + " INT DEFAULT(0), "
- + ConversationColumns.NOTIFICATION_ENABLED + " INT DEFAULT(1), "
- + ConversationColumns.NOTIFICATION_SOUND_URI + " TEXT, "
- + ConversationColumns.NOTIFICATION_VIBRATION + " INT DEFAULT(1), "
- + ConversationColumns.INCLUDE_EMAIL_ADDRESS + " INT DEFAULT(0), "
- + ConversationColumns.SMS_SERVICE_CENTER + " TEXT "
- + ");";
-
- private static final String CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL =
- "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SMS_THREAD_ID
- + " ON " + CONVERSATIONS_TABLE
- + "(" + ConversationColumns.SMS_THREAD_ID + ")";
-
- private static final String CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL =
- "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.ARCHIVE_STATUS
- + " ON " + CONVERSATIONS_TABLE
- + "(" + ConversationColumns.ARCHIVE_STATUS + ")";
-
- private static final String CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL =
- "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SORT_TIMESTAMP
- + " ON " + CONVERSATIONS_TABLE
- + "(" + ConversationColumns.SORT_TIMESTAMP + ")";
-
- // Messages table schema
- public static class MessageColumns implements BaseColumns {
- /* conversation id that this message belongs to */
- public static final String CONVERSATION_ID = "conversation_id";
-
- /* participant which send this message */
- public static final String SENDER_PARTICIPANT_ID = "sender_id";
-
- /* This is bugle's internal status for the message */
- public static final String STATUS = "message_status";
-
- /* Type of message: SMS, MMS or MMS notification */
- public static final String PROTOCOL = "message_protocol";
-
- /* This is the time that the sender sent the message */
- public static final String SENT_TIMESTAMP = "sent_timestamp";
-
- /* Time that we received the message on this device */
- public static final String RECEIVED_TIMESTAMP = "received_timestamp";
-
- /* When the message has been seen by a user in a notification */
- public static final String SEEN = "seen";
-
- /* When the message has been read by a user */
- public static final String READ = "read";
-
- /* participant representing the sim which processed this message */
- public static final String SELF_PARTICIPANT_ID = "self_id";
-
- /*
- * Time when a retry is initiated. This is used to compute the retry window
- * when we retry sending/downloading a message.
- */
- public static final String RETRY_START_TIMESTAMP = "retry_start_timestamp";
-
- // Columns which map to the SMS provider
-
- /* Message ID from the platform provider */
- public static final String SMS_MESSAGE_URI = "sms_message_uri";
-
- /* The message priority for MMS message */
- public static final String SMS_PRIORITY = "sms_priority";
-
- /* The message size for MMS message */
- public static final String SMS_MESSAGE_SIZE = "sms_message_size";
-
- /* The subject for MMS message */
- public static final String MMS_SUBJECT = "mms_subject";
-
- /* Transaction id for MMS notificaiton */
- public static final String MMS_TRANSACTION_ID = "mms_transaction_id";
-
- /* Content location for MMS notificaiton */
- public static final String MMS_CONTENT_LOCATION = "mms_content_location";
-
- /* The expiry time (ms) for MMS message */
- public static final String MMS_EXPIRY = "mms_expiry";
-
- /* The detailed status (RESPONSE_STATUS or RETRIEVE_STATUS) for MMS message */
- public static final String RAW_TELEPHONY_STATUS = "raw_status";
- }
-
- // Messages table SQL
- private static final String CREATE_MESSAGES_TABLE_SQL =
- "CREATE TABLE " + MESSAGES_TABLE + " ("
- + MessageColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + MessageColumns.CONVERSATION_ID + " INT, "
- + MessageColumns.SENDER_PARTICIPANT_ID + " INT, "
- + MessageColumns.SENT_TIMESTAMP + " INT DEFAULT(0), "
- + MessageColumns.RECEIVED_TIMESTAMP + " INT DEFAULT(0), "
- + MessageColumns.PROTOCOL + " INT DEFAULT(0), "
- + MessageColumns.STATUS + " INT DEFAULT(0), "
- + MessageColumns.SEEN + " INT DEFAULT(0), "
- + MessageColumns.READ + " INT DEFAULT(0), "
- + MessageColumns.SMS_MESSAGE_URI + " TEXT, "
- + MessageColumns.SMS_PRIORITY + " INT DEFAULT(0), "
- + MessageColumns.SMS_MESSAGE_SIZE + " INT DEFAULT(0), "
- + MessageColumns.MMS_SUBJECT + " TEXT, "
- + MessageColumns.MMS_TRANSACTION_ID + " TEXT, "
- + MessageColumns.MMS_CONTENT_LOCATION + " TEXT, "
- + MessageColumns.MMS_EXPIRY + " INT DEFAULT(0), "
- + MessageColumns.RAW_TELEPHONY_STATUS + " INT DEFAULT(0), "
- + MessageColumns.SELF_PARTICIPANT_ID + " INT, "
- + MessageColumns.RETRY_START_TIMESTAMP + " INT DEFAULT(0), "
- + "FOREIGN KEY (" + MessageColumns.CONVERSATION_ID + ") REFERENCES "
- + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ") ON DELETE CASCADE "
- + "FOREIGN KEY (" + MessageColumns.SENDER_PARTICIPANT_ID + ") REFERENCES "
- + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + ") ON DELETE SET NULL "
- + "FOREIGN KEY (" + MessageColumns.SELF_PARTICIPANT_ID + ") REFERENCES "
- + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + ") ON DELETE SET NULL "
- + ");";
-
- // Primary sort index for messages table : by conversation id, status, received timestamp.
- private static final String MESSAGES_TABLE_SORT_INDEX_SQL =
- "CREATE INDEX index_" + MESSAGES_TABLE + "_sort ON " + MESSAGES_TABLE + "("
- + MessageColumns.CONVERSATION_ID + ", "
- + MessageColumns.STATUS + ", "
- + MessageColumns.RECEIVED_TIMESTAMP + ")";
-
- private static final String MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL =
- "CREATE INDEX index_" + MESSAGES_TABLE + "_status_seen ON " + MESSAGES_TABLE + "("
- + MessageColumns.STATUS + ", "
- + MessageColumns.SEEN + ")";
-
- // Parts table schema
- // A part may contain text or a media url, but not both.
- public static class PartColumns implements BaseColumns {
- /* message id that this part belongs to */
- public static final String MESSAGE_ID = "message_id";
-
- /* conversation id that this part belongs to */
- public static final String CONVERSATION_ID = "conversation_id";
-
- /* text for this part */
- public static final String TEXT = "text";
-
- /* content uri for this part */
- public static final String CONTENT_URI = "uri";
-
- /* content type for this part */
- public static final String CONTENT_TYPE = "content_type";
-
- /* cached width for this part (for layout while loading) */
- public static final String WIDTH = "width";
-
- /* cached height for this part (for layout while loading) */
- public static final String HEIGHT = "height";
-
- /* de-normalized copy of timestamp from the messages table. This is populated
- * via an insert trigger on the parts table.
- */
- public static final String TIMESTAMP = "timestamp";
- }
-
- // Message part table SQL
- private static final String CREATE_PARTS_TABLE_SQL =
- "CREATE TABLE " + PARTS_TABLE + "("
- + PartColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + PartColumns.MESSAGE_ID + " INT,"
- + PartColumns.TEXT + " TEXT,"
- + PartColumns.CONTENT_URI + " TEXT,"
- + PartColumns.CONTENT_TYPE + " TEXT,"
- + PartColumns.WIDTH + " INT DEFAULT("
- + MessagingContentProvider.UNSPECIFIED_SIZE + "),"
- + PartColumns.HEIGHT + " INT DEFAULT("
- + MessagingContentProvider.UNSPECIFIED_SIZE + "),"
- + PartColumns.TIMESTAMP + " INT, "
- + PartColumns.CONVERSATION_ID + " INT NOT NULL,"
- + "FOREIGN KEY (" + PartColumns.MESSAGE_ID + ") REFERENCES "
- + MESSAGES_TABLE + "(" + MessageColumns._ID + ") ON DELETE CASCADE "
- + "FOREIGN KEY (" + PartColumns.CONVERSATION_ID + ") REFERENCES "
- + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ") ON DELETE CASCADE "
- + ");";
-
- public static final String CREATE_PARTS_TRIGGER_SQL =
- "CREATE TRIGGER " + PARTS_TABLE + "_TRIGGER" + " AFTER INSERT ON " + PARTS_TABLE
- + " FOR EACH ROW "
- + " BEGIN UPDATE " + PARTS_TABLE
- + " SET " + PartColumns.TIMESTAMP + "="
- + " (SELECT received_timestamp FROM " + MESSAGES_TABLE + " WHERE " + MESSAGES_TABLE
- + "." + MessageColumns._ID + "=" + "NEW." + PartColumns.MESSAGE_ID + ")"
- + " WHERE " + PARTS_TABLE + "." + PartColumns._ID + "=" + "NEW." + PartColumns._ID
- + "; END";
-
- public static final String CREATE_MESSAGES_TRIGGER_SQL =
- "CREATE TRIGGER " + MESSAGES_TABLE + "_TRIGGER" + " AFTER UPDATE OF "
- + MessageColumns.RECEIVED_TIMESTAMP + " ON " + MESSAGES_TABLE
- + " FOR EACH ROW BEGIN UPDATE " + PARTS_TABLE + " SET " + PartColumns.TIMESTAMP
- + " = NEW." + MessageColumns.RECEIVED_TIMESTAMP + " WHERE " + PARTS_TABLE + "."
- + PartColumns.MESSAGE_ID + " = NEW." + MessageColumns._ID
- + "; END;";
-
- // Primary sort index for parts table : by message_id
- private static final String PARTS_TABLE_MESSAGE_INDEX_SQL =
- "CREATE INDEX index_" + PARTS_TABLE + "_message_id ON " + PARTS_TABLE + "("
- + PartColumns.MESSAGE_ID + ")";
-
- // Participants table schema
- public static class ParticipantColumns implements BaseColumns {
- /* The subscription id for the sim associated with this self participant.
- * Introduced in L. For earlier versions will always be default_sub_id (-1).
- * For multi sim devices (or cases where the sim was changed) single device
- * may have several different sub_id values */
- public static final String SUB_ID = "sub_id";
-
- /* The slot of the active SIM (inserted in the device) for this self-participant. If the
- * self-participant doesn't correspond to any active SIM, this will be
- * {@link android.telephony.SubscriptionManager#INVALID_SLOT_ID}.
- * The column is ignored for all non-self participants.
- */
- public static final String SIM_SLOT_ID = "sim_slot_id";
-
- /* The phone number stored in a standard E164 format if possible. This is unique for a
- * given participant. We can't handle multiple participants with the same phone number
- * since we don't know which of them a message comes from. This can also be an email
- * address, in which case this is the same as the displayed address */
- public static final String NORMALIZED_DESTINATION = "normalized_destination";
-
- /* The phone number as originally supplied and used for dialing. Not necessarily in E164
- * format or unique */
- public static final String SEND_DESTINATION = "send_destination";
-
- /* The user-friendly formatting of the phone number according to the region setting of
- * the device when the row was added. */
- public static final String DISPLAY_DESTINATION = "display_destination";
-
- /* A string with this participant's full name or a pretty printed phone number */
- public static final String FULL_NAME = "full_name";
-
- /* A string with just this participant's first name */
- public static final String FIRST_NAME = "first_name";
-
- /* A local URI to an asset for the icon for this participant */
- public static final String PROFILE_PHOTO_URI = "profile_photo_uri";
-
- /* Contact id for matching local contact for this participant */
- public static final String CONTACT_ID = "contact_id";
-
- /* String that contains hints on how to find contact information in a contact lookup */
- public static final String LOOKUP_KEY = "lookup_key";
-
- /* If this participant is blocked */
- public static final String BLOCKED = "blocked";
-
- /* The color of the subscription (FOR SELF PARTICIPANTS ONLY) */
- public static final String SUBSCRIPTION_COLOR = "subscription_color";
-
- /* The name of the subscription (FOR SELF PARTICIPANTS ONLY) */
- public static final String SUBSCRIPTION_NAME = "subscription_name";
-
- /* The exact destination stored in Contacts for this participant */
- public static final String CONTACT_DESTINATION = "contact_destination";
- }
-
- // Participants table SQL
- private static final String CREATE_PARTICIPANTS_TABLE_SQL =
- "CREATE TABLE " + PARTICIPANTS_TABLE + "("
- + ParticipantColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + ParticipantColumns.SUB_ID + " INT DEFAULT("
- + ParticipantData.OTHER_THAN_SELF_SUB_ID + "),"
- + ParticipantColumns.SIM_SLOT_ID + " INT DEFAULT("
- + ParticipantData.INVALID_SLOT_ID + "),"
- + ParticipantColumns.NORMALIZED_DESTINATION + " TEXT,"
- + ParticipantColumns.SEND_DESTINATION + " TEXT,"
- + ParticipantColumns.DISPLAY_DESTINATION + " TEXT,"
- + ParticipantColumns.FULL_NAME + " TEXT,"
- + ParticipantColumns.FIRST_NAME + " TEXT,"
- + ParticipantColumns.PROFILE_PHOTO_URI + " TEXT, "
- + ParticipantColumns.CONTACT_ID + " INT DEFAULT( "
- + ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED + "), "
- + ParticipantColumns.LOOKUP_KEY + " STRING, "
- + ParticipantColumns.BLOCKED + " INT DEFAULT(0), "
- + ParticipantColumns.SUBSCRIPTION_NAME + " TEXT, "
- + ParticipantColumns.SUBSCRIPTION_COLOR + " INT DEFAULT(0), "
- + ParticipantColumns.CONTACT_DESTINATION + " TEXT, "
- + "UNIQUE (" + ParticipantColumns.NORMALIZED_DESTINATION + ", "
- + ParticipantColumns.SUB_ID + ") ON CONFLICT FAIL" + ");";
-
- private static final String CREATE_SELF_PARTICIPANT_SQL =
- "INSERT INTO " + PARTICIPANTS_TABLE
- + " ( " + ParticipantColumns.SUB_ID + " ) VALUES ( %s )";
-
- static String getCreateSelfParticipantSql(int subId) {
- return String.format(CREATE_SELF_PARTICIPANT_SQL, subId);
- }
-
- // Conversation Participants table schema - contains a list of participants excluding the user
- // in a given conversation.
- public static class ConversationParticipantsColumns implements BaseColumns {
- /* participant id of someone in this conversation */
- public static final String PARTICIPANT_ID = "participant_id";
-
- /* conversation id that this participant belongs to */
- public static final String CONVERSATION_ID = "conversation_id";
- }
-
- // Conversation Participants table SQL
- private static final String CREATE_CONVERSATION_PARTICIPANTS_TABLE_SQL =
- "CREATE TABLE " + CONVERSATION_PARTICIPANTS_TABLE + "("
- + ConversationParticipantsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + ConversationParticipantsColumns.CONVERSATION_ID + " INT,"
- + ConversationParticipantsColumns.PARTICIPANT_ID + " INT,"
- + "UNIQUE (" + ConversationParticipantsColumns.CONVERSATION_ID + ","
- + ConversationParticipantsColumns.PARTICIPANT_ID + ") ON CONFLICT FAIL, "
- + "FOREIGN KEY (" + ConversationParticipantsColumns.CONVERSATION_ID + ") "
- + "REFERENCES " + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ")"
- + " ON DELETE CASCADE "
- + "FOREIGN KEY (" + ConversationParticipantsColumns.PARTICIPANT_ID + ")"
- + " REFERENCES " + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + "));";
-
- // Primary access pattern for conversation participants is to look them up for a specific
- // conversation.
- private static final String CONVERSATION_PARTICIPANTS_TABLE_CONVERSATION_ID_INDEX_SQL =
- "CREATE INDEX index_" + CONVERSATION_PARTICIPANTS_TABLE + "_"
- + ConversationParticipantsColumns.CONVERSATION_ID
- + " ON " + CONVERSATION_PARTICIPANTS_TABLE
- + "(" + ConversationParticipantsColumns.CONVERSATION_ID + ")";
-
- // View for getting parts which are for draft messages.
- static final String DRAFT_PARTS_VIEW_SQL = "CREATE VIEW " +
- DRAFT_PARTS_VIEW + " AS SELECT "
- + PARTS_TABLE + '.' + PartColumns._ID
- + " as " + PartColumns._ID + ", "
- + PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
- + " as " + PartColumns.MESSAGE_ID + ", "
- + PARTS_TABLE + '.' + PartColumns.TEXT
- + " as " + PartColumns.TEXT + ", "
- + PARTS_TABLE + '.' + PartColumns.CONTENT_URI
- + " as " + PartColumns.CONTENT_URI + ", "
- + PARTS_TABLE + '.' + PartColumns.CONTENT_TYPE
- + " as " + PartColumns.CONTENT_TYPE + ", "
- + PARTS_TABLE + '.' + PartColumns.WIDTH
- + " as " + PartColumns.WIDTH + ", "
- + PARTS_TABLE + '.' + PartColumns.HEIGHT
- + " as " + PartColumns.HEIGHT + ", "
- + MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID
- + " as " + MessageColumns.CONVERSATION_ID + " "
- + " FROM " + MESSAGES_TABLE + " LEFT JOIN " + PARTS_TABLE + " ON ("
- + MESSAGES_TABLE + "." + MessageColumns._ID
- + "=" + PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ")"
- // Exclude draft messages from main view
- + " WHERE " + MESSAGES_TABLE + "." + MessageColumns.STATUS
- + " = " + MessageData.BUGLE_STATUS_OUTGOING_DRAFT;
-
- // List of all our SQL tables
- private static final String[] CREATE_TABLE_SQLS = new String[] {
- CREATE_CONVERSATIONS_TABLE_SQL,
- CREATE_MESSAGES_TABLE_SQL,
- CREATE_PARTS_TABLE_SQL,
- CREATE_PARTICIPANTS_TABLE_SQL,
- CREATE_CONVERSATION_PARTICIPANTS_TABLE_SQL,
- };
-
- // List of all our indices
- private static final String[] CREATE_INDEX_SQLS = new String[] {
- CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL,
- CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL,
- CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL,
- MESSAGES_TABLE_SORT_INDEX_SQL,
- MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL,
- PARTS_TABLE_MESSAGE_INDEX_SQL,
- CONVERSATION_PARTICIPANTS_TABLE_CONVERSATION_ID_INDEX_SQL,
- };
-
- // List of all our SQL triggers
- private static final String[] CREATE_TRIGGER_SQLS = new String[] {
- CREATE_PARTS_TRIGGER_SQL,
- CREATE_MESSAGES_TRIGGER_SQL,
- };
-
- // List of all our views
- private static final String[] CREATE_VIEW_SQLS = new String[] {
- ConversationListItemData.getConversationListViewSql(),
- ConversationImagePartsView.getCreateSql(),
- DRAFT_PARTS_VIEW_SQL,
- };
-
- private static final Object sLock = new Object();
- private final Context mApplicationContext;
- private static DatabaseHelper sHelperInstance; // Protected by sLock.
-
- private final Object mDatabaseWrapperLock = new Object();
- private DatabaseWrapper mDatabaseWrapper; // Protected by mDatabaseWrapperLock.
- private final DatabaseUpgradeHelper mUpgradeHelper = new DatabaseUpgradeHelper();
-
- /**
- * Get a (singleton) instance of {@link DatabaseHelper}, creating one if there isn't one yet.
- * This is the only public method for getting a new instance of the class.
- * @param context Should be the application context (or something that will live for the
- * lifetime of the application).
- * @return The current (or a new) DatabaseHelper instance.
- */
- public static DatabaseHelper getInstance(final Context context) {
- synchronized (sLock) {
- if (sHelperInstance == null) {
- sHelperInstance = new DatabaseHelper(context);
- }
- return sHelperInstance;
- }
- }
-
- /**
- * Private constructor, used from {@link #getInstance()}.
- * @param context Should be the application context (or something that will live for the
- * lifetime of the application).
- */
- private DatabaseHelper(final Context context) {
- super(context, DATABASE_NAME, null, getDatabaseVersion(context), null);
- mApplicationContext = context;
- }
-
- /**
- * Test method that always instantiates a new DatabaseHelper instance. This should
- * be used ONLY by the tests and never by the real application.
- * @param context Test context.
- * @return Brand new DatabaseHelper instance.
- */
- @VisibleForTesting
- static DatabaseHelper getNewInstanceForTest(final Context context) {
- Assert.isEngBuild();
- Assert.isTrue(BugleApplication.isRunningTests());
- return new DatabaseHelper(context);
- }
-
- /**
- * Get the (singleton) instance of @{link DatabaseWrapper}.
- * <p>The database is always opened as a writeable database.
- * @return The current (or a new) DatabaseWrapper instance.
- */
- @DoesNotRunOnMainThread
- DatabaseWrapper getDatabase() {
- // We prevent the main UI thread from accessing the database here since we have to allow
- // public access to this class to enable sub-packages to access data.
- Assert.isNotMainThread();
-
- synchronized (mDatabaseWrapperLock) {
- if (mDatabaseWrapper == null) {
- mDatabaseWrapper = new DatabaseWrapper(mApplicationContext, getWritableDatabase());
- }
- return mDatabaseWrapper;
- }
- }
-
- @Override
- public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
- mUpgradeHelper.onDowngrade(db, oldVersion, newVersion);
- }
-
- /**
- * Drops and recreates all tables.
- */
- public static void rebuildTables(final SQLiteDatabase db) {
- // Drop tables first, then views, and indices.
- dropAllTables(db);
- dropAllViews(db);
- dropAllIndexes(db);
- dropAllTriggers(db);
-
- // Recreate the whole database.
- createDatabase(db);
- }
-
- /**
- * Drop and rebuild a given view.
- */
- static void rebuildView(final SQLiteDatabase db, final String viewName,
- final String createViewSql) {
- dropView(db, viewName, true /* throwOnFailure */);
- db.execSQL(createViewSql);
- }
-
- private static void dropView(final SQLiteDatabase db, final String viewName,
- final boolean throwOnFailure) {
- final String dropPrefix = "DROP VIEW IF EXISTS ";
- try {
- db.execSQL(dropPrefix + viewName);
- } catch (final SQLException ex) {
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop view " + viewName + " "
- + ex);
- }
-
- if (throwOnFailure) {
- throw ex;
- }
- }
- }
-
- /**
- * Drops all user-defined tables from the given database.
- */
- private static void dropAllTables(final SQLiteDatabase db) {
- final Cursor tableCursor =
- db.query(MASTER_TABLE, MASTER_COLUMNS, "type='table'", null, null, null, null);
- if (tableCursor != null) {
- try {
- final String dropPrefix = "DROP TABLE IF EXISTS ";
- while (tableCursor.moveToNext()) {
- final String tableName = tableCursor.getString(0);
-
- // Skip special tables
- if (tableName.startsWith("android_") || tableName.startsWith("sqlite_")) {
- continue;
- }
- try {
- db.execSQL(dropPrefix + tableName);
- } catch (final SQLException ex) {
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop table " + tableName + " "
- + ex);
- }
- }
- }
- } finally {
- tableCursor.close();
- }
- }
- }
-
- /**
- * Drops all user-defined triggers from the given database.
- */
- private static void dropAllTriggers(final SQLiteDatabase db) {
- final Cursor triggerCursor =
- db.query(MASTER_TABLE, MASTER_COLUMNS, "type='trigger'", null, null, null, null);
- if (triggerCursor != null) {
- try {
- final String dropPrefix = "DROP TRIGGER IF EXISTS ";
- while (triggerCursor.moveToNext()) {
- final String triggerName = triggerCursor.getString(0);
-
- // Skip special tables
- if (triggerName.startsWith("android_") || triggerName.startsWith("sqlite_")) {
- continue;
- }
- try {
- db.execSQL(dropPrefix + triggerName);
- } catch (final SQLException ex) {
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop trigger " + triggerName +
- " " + ex);
- }
- }
- }
- } finally {
- triggerCursor.close();
- }
- }
- }
-
- /**
- * Drops all user-defined views from the given database.
- */
- private static void dropAllViews(final SQLiteDatabase db) {
- final Cursor viewCursor =
- db.query(MASTER_TABLE, MASTER_COLUMNS, "type='view'", null, null, null, null);
- if (viewCursor != null) {
- try {
- while (viewCursor.moveToNext()) {
- final String viewName = viewCursor.getString(0);
- dropView(db, viewName, false /* throwOnFailure */);
- }
- } finally {
- viewCursor.close();
- }
- }
- }
-
- /**
- * Drops all user-defined views from the given database.
- */
- private static void dropAllIndexes(final SQLiteDatabase db) {
- final Cursor indexCursor =
- db.query(MASTER_TABLE, MASTER_COLUMNS, "type='index'", null, null, null, null);
- if (indexCursor != null) {
- try {
- final String dropPrefix = "DROP INDEX IF EXISTS ";
- while (indexCursor.moveToNext()) {
- final String indexName = indexCursor.getString(0);
- try {
- db.execSQL(dropPrefix + indexName);
- } catch (final SQLException ex) {
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop index " + indexName + " "
- + ex);
- }
- }
- }
- } finally {
- indexCursor.close();
- }
- }
- }
-
- private static void createDatabase(final SQLiteDatabase db) {
- for (final String sql : CREATE_TABLE_SQLS) {
- db.execSQL(sql);
- }
-
- for (final String sql : CREATE_INDEX_SQLS) {
- db.execSQL(sql);
- }
-
- for (final String sql : CREATE_VIEW_SQLS) {
- db.execSQL(sql);
- }
-
- for (final String sql : CREATE_TRIGGER_SQLS) {
- db.execSQL(sql);
- }
-
- // Enable foreign key constraints
- db.execSQL("PRAGMA foreign_keys=ON;");
-
- // Add the default self participant. The default self will be assigned a proper slot id
- // during participant refresh.
- db.execSQL(getCreateSelfParticipantSql(ParticipantData.DEFAULT_SELF_SUB_ID));
-
- DataModel.get().onCreateTables(db);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- createDatabase(db);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- mUpgradeHelper.doOnUpgrade(db, oldVersion, newVersion);
- }
-}
diff --git a/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java b/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java
deleted file mode 100644
index d112533..0000000
--- a/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel;
-
-import android.database.sqlite.SQLiteDatabase;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-public class DatabaseUpgradeHelper {
- private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
-
- public void doOnUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
- Assert.isTrue(newVersion >= oldVersion);
- if (oldVersion == newVersion) {
- return;
- }
-
- LogUtil.i(TAG, "Database upgrade started from version " + oldVersion + " to " + newVersion);
-
- // Add future upgrade code here
- }
-
- public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
- DatabaseHelper.rebuildTables(db);
- LogUtil.e(TAG, "Database downgrade requested for version " +
- oldVersion + " version " + newVersion + ", forcing db rebuild!");
- }
-}
diff --git a/src/com/android/messaging/datamodel/DatabaseWrapper.java b/src/com/android/messaging/datamodel/DatabaseWrapper.java
deleted file mode 100644
index ca7a331..0000000
--- a/src/com/android/messaging/datamodel/DatabaseWrapper.java
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteFullException;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.database.sqlite.SQLiteStatement;
-import android.util.SparseArray;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.Locale;
-import java.util.Stack;
-import java.util.regex.Pattern;
-
-public class DatabaseWrapper {
- private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
-
- private final SQLiteDatabase mDatabase;
- private final Context mContext;
- private final boolean mLog;
- /**
- * Set mExplainQueryPlanRegexp (via {@link BugleGservicesKeys#EXPLAIN_QUERY_PLAN_REGEXP}
- * to regex matching queries to see query plans. For example, ".*" to show all query plans.
- */
- // See
- private final String mExplainQueryPlanRegexp;
- private static final int sTimingThreshold = 50; // in milliseconds
-
- public static final int INDEX_INSERT_MESSAGE_PART = 0;
- public static final int INDEX_INSERT_MESSAGE = 1;
- public static final int INDEX_QUERY_CONVERSATIONS_LATEST_MESSAGE = 2;
- public static final int INDEX_QUERY_MESSAGES_LATEST_MESSAGE = 3;
-
- private final SparseArray<SQLiteStatement> mCompiledStatements;
-
- static class TransactionData {
- long time;
- boolean transactionSuccessful;
- }
-
- // track transaction on a per thread basis
- private static ThreadLocal<Stack<TransactionData>> sTransactionDepth =
- new ThreadLocal<Stack<TransactionData>>() {
- @Override
- public Stack<TransactionData> initialValue() {
- return new Stack<TransactionData>();
- }
- };
-
- private static String[] sFormatStrings = new String[] {
- "took %d ms to %s",
- " took %d ms to %s",
- " took %d ms to %s",
- };
-
- DatabaseWrapper(final Context context, final SQLiteDatabase db) {
- mLog = LogUtil.isLoggable(LogUtil.BUGLE_DATABASE_PERF_TAG, LogUtil.VERBOSE);
- mExplainQueryPlanRegexp = Factory.get().getBugleGservices().getString(
- BugleGservicesKeys.EXPLAIN_QUERY_PLAN_REGEXP, null);
- mDatabase = db;
- mContext = context;
- mCompiledStatements = new SparseArray<SQLiteStatement>();
- }
-
- public SQLiteStatement getStatementInTransaction(final int index, final String statement) {
- // Use transaction to serialize access to statements
- Assert.isTrue(mDatabase.inTransaction());
- SQLiteStatement compiled = mCompiledStatements.get(index);
- if (compiled == null) {
- compiled = mDatabase.compileStatement(statement);
- Assert.isTrue(compiled.toString().contains(statement.trim()));
- mCompiledStatements.put(index, compiled);
- }
- return compiled;
- }
-
- private void maybePlayDebugNoise() {
- DebugUtils.maybePlayDebugNoise(mContext, DebugUtils.DEBUG_SOUND_DB_OP);
- }
-
- private static void printTiming(final long t1, final String msg) {
- final int transactionDepth = sTransactionDepth.get().size();
- final long t2 = System.currentTimeMillis();
- final long delta = t2 - t1;
- if (delta > sTimingThreshold) {
- LogUtil.v(LogUtil.BUGLE_DATABASE_PERF_TAG, String.format(Locale.US,
- sFormatStrings[Math.min(sFormatStrings.length - 1, transactionDepth)],
- delta,
- msg));
- }
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public void beginTransaction() {
- final long t1 = System.currentTimeMillis();
-
- // push the current time onto the transaction stack
- final TransactionData f = new TransactionData();
- f.time = t1;
- sTransactionDepth.get().push(f);
-
- mDatabase.beginTransaction();
- }
-
- public void setTransactionSuccessful() {
- final TransactionData f = sTransactionDepth.get().peek();
- f.transactionSuccessful = true;
- mDatabase.setTransactionSuccessful();
- }
-
- public void endTransaction() {
- long t1 = 0;
- long transactionStartTime = 0;
- final TransactionData f = sTransactionDepth.get().pop();
- if (f.transactionSuccessful == false) {
- LogUtil.w(TAG, "endTransaction without setting successful");
- for (final StackTraceElement st : (new Exception()).getStackTrace()) {
- LogUtil.w(TAG, " " + st.toString());
- }
- }
- if (mLog) {
- transactionStartTime = f.time;
- t1 = System.currentTimeMillis();
- }
- try {
- mDatabase.endTransaction();
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to endTransaction", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
- if (mLog) {
- printTiming(t1, String.format(Locale.US,
- ">>> endTransaction (total for this transaction: %d)",
- (System.currentTimeMillis() - transactionStartTime)));
- }
- }
-
- public void yieldTransaction() {
- long yieldStartTime = 0;
- if (mLog) {
- yieldStartTime = System.currentTimeMillis();
- }
- final boolean wasYielded = mDatabase.yieldIfContendedSafely();
- if (wasYielded && mLog) {
- printTiming(yieldStartTime, "yieldTransaction");
- }
- }
-
- public void insertWithOnConflict(final String searchTable, final String nullColumnHack,
- final ContentValues initialValues, final int conflictAlgorithm) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- try {
- mDatabase.insertWithOnConflict(searchTable, nullColumnHack, initialValues,
- conflictAlgorithm);
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to insertWithOnConflict", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
- if (mLog) {
- printTiming(t1, String.format(Locale.US,
- "insertWithOnConflict with ", searchTable));
- }
- }
-
- private void explainQueryPlan(final SQLiteQueryBuilder qb, final SQLiteDatabase db,
- final String[] projection, final String selection,
- @SuppressWarnings("unused")
- final String[] queryArgs,
- final String groupBy,
- @SuppressWarnings("unused")
- final String having,
- final String sortOrder, final String limit) {
- final String queryString = qb.buildQuery(
- projection,
- selection,
- groupBy,
- null/*having*/,
- sortOrder,
- limit);
- explainQueryPlan(db, queryString, queryArgs);
- }
-
- private void explainQueryPlan(final SQLiteDatabase db, final String sql,
- final String[] queryArgs) {
- if (!Pattern.matches(mExplainQueryPlanRegexp, sql)) {
- return;
- }
- final Cursor planCursor = db.rawQuery("explain query plan " + sql, queryArgs);
- try {
- if (planCursor != null && planCursor.moveToFirst()) {
- final int detailColumn = planCursor.getColumnIndex("detail");
- final StringBuilder sb = new StringBuilder();
- do {
- sb.append(planCursor.getString(detailColumn));
- sb.append("\n");
- } while (planCursor.moveToNext());
- if (sb.length() > 0) {
- sb.setLength(sb.length() - 1);
- }
- LogUtil.v(TAG, "for query " + sql + "\nplan is: "
- + sb.toString());
- }
- } catch (final Exception e) {
- LogUtil.w(TAG, "Query plan failed ", e);
- } finally {
- if (planCursor != null) {
- planCursor.close();
- }
- }
- }
-
- public Cursor query(final String searchTable, final String[] projection,
- final String selection, final String[] selectionArgs, final String groupBy,
- final String having, final String orderBy, final String limit) {
- if (mExplainQueryPlanRegexp != null) {
- final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables(searchTable);
- explainQueryPlan(qb, mDatabase, projection, selection, selectionArgs,
- groupBy, having, orderBy, limit);
- }
-
- maybePlayDebugNoise();
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- final Cursor cursor = mDatabase.query(searchTable, projection, selection, selectionArgs,
- groupBy, having, orderBy, limit);
- if (mLog) {
- printTiming(
- t1,
- String.format(Locale.US, "query %s with %s ==> %d",
- searchTable, selection, cursor.getCount()));
- }
- return cursor;
- }
-
- public Cursor query(final String searchTable, final String[] columns,
- final String selection, final String[] selectionArgs, final String groupBy,
- final String having, final String orderBy) {
- return query(
- searchTable, columns, selection, selectionArgs,
- groupBy, having, orderBy, null);
- }
-
- public Cursor query(final SQLiteQueryBuilder qb,
- final String[] projection, final String selection, final String[] queryArgs,
- final String groupBy, final String having, final String sortOrder, final String limit) {
- if (mExplainQueryPlanRegexp != null) {
- explainQueryPlan(qb, mDatabase, projection, selection, queryArgs,
- groupBy, having, sortOrder, limit);
- }
- maybePlayDebugNoise();
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- final Cursor cursor = qb.query(mDatabase, projection, selection, queryArgs, groupBy,
- having, sortOrder, limit);
- if (mLog) {
- printTiming(
- t1,
- String.format(Locale.US, "query %s with %s ==> %d",
- qb.getTables(), selection, cursor.getCount()));
- }
- return cursor;
- }
-
- public long queryNumEntries(final String table, final String selection,
- final String[] selectionArgs) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- final long retval =
- DatabaseUtils.queryNumEntries(mDatabase, table, selection, selectionArgs);
- if (mLog){
- printTiming(
- t1,
- String.format(Locale.US, "queryNumEntries %s with %s ==> %d", table,
- selection, retval));
- }
- return retval;
- }
-
- public Cursor rawQuery(final String sql, final String[] args) {
- if (mExplainQueryPlanRegexp != null) {
- explainQueryPlan(mDatabase, sql, args);
- }
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- final Cursor cursor = mDatabase.rawQuery(sql, args);
- if (mLog) {
- printTiming(
- t1,
- String.format(Locale.US, "rawQuery %s ==> %d", sql, cursor.getCount()));
- }
- return cursor;
- }
-
- public int update(final String table, final ContentValues values,
- final String selection, final String[] selectionArgs) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- int count = 0;
- try {
- count = mDatabase.update(table, values, selection, selectionArgs);
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to update", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
- if (mLog) {
- printTiming(t1, String.format(Locale.US, "update %s with %s ==> %d",
- table, selection, count));
- }
- return count;
- }
-
- public int delete(final String table, final String whereClause, final String[] whereArgs) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- int count = 0;
- try {
- count = mDatabase.delete(table, whereClause, whereArgs);
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to delete", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
- if (mLog) {
- printTiming(t1,
- String.format(Locale.US, "delete from %s with %s ==> %d", table,
- whereClause, count));
- }
- return count;
- }
-
- public long insert(final String table, final String nullColumnHack,
- final ContentValues values) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- long rowId = -1;
- try {
- rowId = mDatabase.insert(table, nullColumnHack, values);
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to insert", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
- if (mLog) {
- printTiming(t1, String.format(Locale.US, "insert to %s", table));
- }
- return rowId;
- }
-
- public long replace(final String table, final String nullColumnHack,
- final ContentValues values) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- long rowId = -1;
- try {
- rowId = mDatabase.replace(table, nullColumnHack, values);
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to replace", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
- if (mLog) {
- printTiming(t1, String.format(Locale.US, "replace to %s", table));
- }
- return rowId;
- }
-
- public void setLocale(final Locale locale) {
- mDatabase.setLocale(locale);
- }
-
- public void execSQL(final String sql, final String[] bindArgs) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- try {
- mDatabase.execSQL(sql, bindArgs);
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to execSQL", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
-
- if (mLog) {
- printTiming(t1, String.format(Locale.US, "execSQL %s", sql));
- }
- }
-
- public void execSQL(final String sql) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- try {
- mDatabase.execSQL(sql);
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to execSQL", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
-
- if (mLog) {
- printTiming(t1, String.format(Locale.US, "execSQL %s", sql));
- }
- }
-
- public int execSQLUpdateDelete(final String sql) {
- long t1 = 0;
- if (mLog) {
- t1 = System.currentTimeMillis();
- }
- maybePlayDebugNoise();
- final SQLiteStatement statement = mDatabase.compileStatement(sql);
- int rowsUpdated = 0;
- try {
- rowsUpdated = statement.executeUpdateDelete();
- } catch (SQLiteFullException ex) {
- LogUtil.e(TAG, "Database full, unable to execSQLUpdateDelete", ex);
- UiUtils.showToastAtBottom(R.string.db_full);
- }
- if (mLog) {
- printTiming(t1, String.format(Locale.US, "execSQLUpdateDelete %s", sql));
- }
- return rowsUpdated;
- }
-
- public SQLiteDatabase getDatabase() {
- return mDatabase;
- }
-}
diff --git a/src/com/android/messaging/datamodel/FileProvider.java b/src/com/android/messaging/datamodel/FileProvider.java
deleted file mode 100644
index ee332cd..0000000
--- a/src/com/android/messaging/datamodel/FileProvider.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Random;
-
-/**
- * A very simple content provider that can serve files.
- */
-public abstract class FileProvider extends ContentProvider {
- // Object to generate random id for temp images.
- private static final Random RANDOM_ID = new Random();
-
- abstract File getFile(final String path, final String extension);
-
- private static final String FILE_EXTENSION_PARAM_KEY = "ext";
-
- /**
- * Check if filename conforms to requirement for our provider
- * @param fileId filename (optionally starting with path character
- * @return true if filename consists only of digits
- */
- protected static boolean isValidFileId(final String fileId) {
- // Ignore initial "/"
- for (int index = (fileId.startsWith("/") ? 1 : 0); index < fileId.length(); index++) {
- final Character c = fileId.charAt(index);
- if (!Character.isDigit(c)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Create a temp file (to allow writing to that one particular file)
- * @param file the file to create
- * @return true if file successfully created
- */
- protected static boolean ensureFileExists(final File file) {
- try {
- final File parentDir = file.getParentFile();
- if (parentDir.exists() || parentDir.mkdirs()) {
- return file.createNewFile();
- }
- } catch (final IOException e) {
- // fail on exceptions creating the file
- }
- return false;
- }
-
- /**
- * Build uri for a new temporary file (creating file)
- * @param authority authority with which to populate uri
- * @param extension optional file extension
- * @return unique uri that can be used to write temporary files
- */
- protected static Uri buildFileUri(final String authority, final String extension) {
- final long fileId = Math.abs(RANDOM_ID.nextLong());
- final Uri.Builder builder = (new Uri.Builder()).authority(authority).scheme(
- ContentResolver.SCHEME_CONTENT);
- builder.appendPath(String.valueOf(fileId));
- if (!TextUtils.isEmpty(extension)) {
- builder.appendQueryParameter(FILE_EXTENSION_PARAM_KEY, extension);
- }
- return builder.build();
- }
-
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
- final String fileId = uri.getPath();
- if (isValidFileId(fileId)) {
- final File file = getFile(fileId, getExtensionFromUri(uri));
- return file.delete() ? 1 : 0;
- }
- return 0;
- }
-
- @Override
- public ParcelFileDescriptor openFile(final Uri uri, final String fileMode)
- throws FileNotFoundException {
- final String fileId = uri.getPath();
- if (isValidFileId(fileId)) {
- final File file = getFile(fileId, getExtensionFromUri(uri));
- final int mode =
- (TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
- ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
- return ParcelFileDescriptor.open(file, mode);
- }
- return null;
- }
-
- protected static String getExtensionFromUri(final Uri uri) {
- return uri.getQueryParameter(FILE_EXTENSION_PARAM_KEY);
- }
-
- @Override
- public Cursor query(final Uri uri, final String[] projection, final String selection,
- final String[] selectionArgs, final String sortOrder) {
- // Don't support queries.
- return null;
- }
-
- @Override
- public Uri insert(final Uri uri, final ContentValues values) {
- // Don't support inserts.
- return null;
- }
-
- @Override
- public int update(final Uri uri, final ContentValues values, final String selection,
- final String[] selectionArgs) {
- // Don't support updates.
- return 0;
- }
-
- @Override
- public String getType(final Uri uri) {
- // No need for mime types.
- return null;
- }
-}
diff --git a/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java b/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java
deleted file mode 100644
index 62483a0..0000000
--- a/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel;
-
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.support.v4.util.SimpleArrayMap;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContactUtil;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-
-/**
- * A cursor builder that takes the frequent contacts cursor and aggregate it with the all contacts
- * cursor to fill in contact details such as phone numbers and strip away invalid contacts.
- *
- * Because the frequent contact list depends on the loading of two cursors, it needs to temporarily
- * store the cursor that it receives with setFrequents() and setAllContacts() calls. Because it
- * doesn't know which one will be finished first, it always checks whether both cursors are ready
- * to pull data from and construct the aggregate cursor when it's ready to do so. Note that
- * this cursor builder doesn't assume ownership of the cursors passed in - it merely references
- * them and always does a isClosed() check before consuming them. The ownership still belongs to
- * the loader framework and the cursor may be closed when the UI is torn down.
- */
-public class FrequentContactsCursorBuilder {
- private Cursor mAllContactsCursor;
- private Cursor mFrequentContactsCursor;
-
- /**
- * Sets the frequent contacts cursor as soon as it is loaded, or null if it's reset.
- * @return this builder instance for chained operations
- */
- public FrequentContactsCursorBuilder setFrequents(final Cursor frequentContactsCursor) {
- mFrequentContactsCursor = frequentContactsCursor;
- return this;
- }
-
- /**
- * Sets the all contacts cursor as soon as it is loaded, or null if it's reset.
- * @return this builder instance for chained operations
- */
- public FrequentContactsCursorBuilder setAllContacts(final Cursor allContactsCursor) {
- mAllContactsCursor = allContactsCursor;
- return this;
- }
-
- /**
- * Reset this builder. Must be called when the consumer resets its data.
- */
- public void resetBuilder() {
- mAllContactsCursor = null;
- mFrequentContactsCursor = null;
- }
-
- /**
- * Attempt to build the cursor records from the frequent and all contacts cursor if they
- * are both ready to be consumed.
- * @return the frequent contact cursor if built successfully, or null if it can't be built yet.
- */
- public Cursor build() {
- if (mFrequentContactsCursor != null && mAllContactsCursor != null) {
- Assert.isTrue(!mFrequentContactsCursor.isClosed());
- Assert.isTrue(!mAllContactsCursor.isClosed());
-
- // Frequent contacts cursor has one record per contact, plus it doesn't contain info
- // such as phone number and type. In order for the records to be usable by Bugle, we
- // would like to populate it with information from the all contacts cursor.
- final MatrixCursor retCursor = new MatrixCursor(ContactUtil.PhoneQuery.PROJECTION);
-
- // First, go through the frequents cursor and take note of all lookup keys and their
- // corresponding rank in the frequents list.
- final SimpleArrayMap<String, Integer> lookupKeyToRankMap =
- new SimpleArrayMap<String, Integer>();
- int oldPosition = mFrequentContactsCursor.getPosition();
- int rank = 0;
- mFrequentContactsCursor.moveToPosition(-1);
- while (mFrequentContactsCursor.moveToNext()) {
- final String lookupKey = mFrequentContactsCursor.getString(
- ContactUtil.INDEX_LOOKUP_KEY_FREQUENT);
- lookupKeyToRankMap.put(lookupKey, rank++);
- }
- mFrequentContactsCursor.moveToPosition(oldPosition);
-
- // Second, go through the all contacts cursor once and retrieve all information
- // (multiple phone numbers etc.) and store that in an array list. Since the all
- // contacts list only contains phone contacts, this step will ensure that we filter
- // out any invalid/email contacts in the frequents list.
- final ArrayList<Object[]> rows =
- new ArrayList<Object[]>(mFrequentContactsCursor.getCount());
- oldPosition = mAllContactsCursor.getPosition();
- mAllContactsCursor.moveToPosition(-1);
- while (mAllContactsCursor.moveToNext()) {
- final String lookupKey = mAllContactsCursor.getString(ContactUtil.INDEX_LOOKUP_KEY);
- if (lookupKeyToRankMap.containsKey(lookupKey)) {
- final Object[] row = new Object[ContactUtil.PhoneQuery.PROJECTION.length];
- row[ContactUtil.INDEX_DATA_ID] =
- mAllContactsCursor.getLong(ContactUtil.INDEX_DATA_ID);
- row[ContactUtil.INDEX_CONTACT_ID] =
- mAllContactsCursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- row[ContactUtil.INDEX_LOOKUP_KEY] =
- mAllContactsCursor.getString(ContactUtil.INDEX_LOOKUP_KEY);
- row[ContactUtil.INDEX_DISPLAY_NAME] =
- mAllContactsCursor.getString(ContactUtil.INDEX_DISPLAY_NAME);
- row[ContactUtil.INDEX_PHOTO_URI] =
- mAllContactsCursor.getString(ContactUtil.INDEX_PHOTO_URI);
- row[ContactUtil.INDEX_PHONE_EMAIL] =
- mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL);
- row[ContactUtil.INDEX_PHONE_EMAIL_TYPE] =
- mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
- row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] =
- mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL);
- rows.add(row);
- }
- }
- mAllContactsCursor.moveToPosition(oldPosition);
-
- // Now we have a list of rows containing frequent contacts in alphabetical order.
- // Therefore, sort all the rows according to their actual ranks in the frequents list.
- Collections.sort(rows, new Comparator<Object[]>() {
- @Override
- public int compare(final Object[] lhs, final Object[] rhs) {
- final String lookupKeyLhs = (String) lhs[ContactUtil.INDEX_LOOKUP_KEY];
- final String lookupKeyRhs = (String) rhs[ContactUtil.INDEX_LOOKUP_KEY];
- Assert.isTrue(lookupKeyToRankMap.containsKey(lookupKeyLhs) &&
- lookupKeyToRankMap.containsKey(lookupKeyRhs));
- final int rankLhs = lookupKeyToRankMap.get(lookupKeyLhs);
- final int rankRhs = lookupKeyToRankMap.get(lookupKeyRhs);
- if (rankLhs < rankRhs) {
- return -1;
- } else if (rankLhs > rankRhs) {
- return 1;
- } else {
- // Same rank, so it's two contact records for the same contact.
- // Perform secondary sorting on the phone type. Always place
- // mobile before everything else.
- final int phoneTypeLhs = (int) lhs[ContactUtil.INDEX_PHONE_EMAIL_TYPE];
- final int phoneTypeRhs = (int) rhs[ContactUtil.INDEX_PHONE_EMAIL_TYPE];
- if (phoneTypeLhs == Phone.TYPE_MOBILE &&
- phoneTypeRhs == Phone.TYPE_MOBILE) {
- return 0;
- } else if (phoneTypeLhs == Phone.TYPE_MOBILE) {
- return -1;
- } else if (phoneTypeRhs == Phone.TYPE_MOBILE) {
- return 1;
- } else {
- // Use the default sort order, i.e. sort by phoneType value.
- return phoneTypeLhs < phoneTypeRhs ? -1 :
- (phoneTypeLhs == phoneTypeRhs ? 0 : 1);
- }
- }
- }
- });
-
- // Finally, add all the rows to this cursor.
- for (final Object[] row : rows) {
- retCursor.addRow(row);
- }
- return retCursor;
- }
- return null;
- }
-}
diff --git a/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java b/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java
deleted file mode 100644
index d1759ad..0000000
--- a/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-
-import com.android.messaging.util.FallbackStrategies;
-import com.android.messaging.util.FallbackStrategies.Strategy;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Helper for querying frequent (and/or starred) contacts.
- */
-public class FrequentContactsCursorQueryData extends CursorQueryData {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static class FrequentContactsCursorLoader extends BoundCursorLoader {
- private final Uri mOriginalUri;
-
- FrequentContactsCursorLoader(String bindingId, Context context, Uri uri,
- String[] projection, String selection, String[] selectionArgs, String sortOrder) {
- super(bindingId, context, uri, projection, selection, selectionArgs, sortOrder);
- mOriginalUri = uri;
- }
-
- @Override
- public Cursor loadInBackground() {
- return FallbackStrategies
- .startWith(new PrimaryStrequentContactsQueryStrategy())
- .thenTry(new FrequentOnlyContactsQueryStrategy())
- .thenTry(new PhoneOnlyStrequentContactsQueryStrategy())
- .execute(null);
- }
-
- private abstract class StrequentContactsQueryStrategy implements Strategy<Void, Cursor> {
- @Override
- public Cursor execute(Void params) throws Exception {
- final Uri uri = getUri();
- if (uri != null) {
- setUri(uri);
- }
- return FrequentContactsCursorLoader.super.loadInBackground();
- }
- protected abstract Uri getUri();
- }
-
- private class PrimaryStrequentContactsQueryStrategy extends StrequentContactsQueryStrategy {
- @Override
- protected Uri getUri() {
- // Use the original URI requested.
- return mOriginalUri;
- }
- }
-
- private class FrequentOnlyContactsQueryStrategy extends StrequentContactsQueryStrategy {
- @Override
- protected Uri getUri() {
- // Some phones have a buggy implementation of the Contacts provider which crashes
- // when we query for strequent (starred+frequent) contacts (b/17991485).
- // If this happens, switch to just querying for frequent contacts.
- return Contacts.CONTENT_FREQUENT_URI;
- }
- }
-
- private class PhoneOnlyStrequentContactsQueryStrategy extends
- StrequentContactsQueryStrategy {
- @Override
- protected Uri getUri() {
- // Some 3rd party ROMs have content provider
- // implementation where invalid SQL queries are returned for regular strequent
- // queries. Using strequent_phone_only query as a fallback to display only phone
- // contacts. This is the last-ditch effort; if this fails, we will display an
- // empty frequent list (b/18354836).
- final String strequentQueryParam = OsUtil.isAtLeastL() ?
- ContactsContract.STREQUENT_PHONE_ONLY : "strequent_phone_only";
- // TODO: Handle enterprise contacts post M once contacts provider supports it
- return Contacts.CONTENT_STREQUENT_URI.buildUpon()
- .appendQueryParameter(strequentQueryParam, "true").build();
- }
- }
- }
-
- public FrequentContactsCursorQueryData(Context context, String[] projection,
- String selection, String[] selectionArgs, String sortOrder) {
- // TODO: Handle enterprise contacts post M once contacts provider supports it
- super(context, Contacts.CONTENT_STREQUENT_URI, projection, selection, selectionArgs,
- sortOrder);
- }
-
- @Override
- public BoundCursorLoader createBoundCursorLoader(String bindingId) {
- return new FrequentContactsCursorLoader(bindingId, mContext, mUri, mProjection, mSelection,
- mSelectionArgs, mSortOrder);
- }
-}
diff --git a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
deleted file mode 100644
index 28ec303..0000000
--- a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.net.Uri;
-import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.Images.Media;
-
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.google.common.base.Joiner;
-
-/**
- * A BoundCursorLoader that reads local media on the device.
- */
-public class GalleryBoundCursorLoader extends BoundCursorLoader {
- public static final String MEDIA_SCANNER_VOLUME_EXTERNAL = "external";
- private static final Uri STORAGE_URI = Files.getContentUri(MEDIA_SCANNER_VOLUME_EXTERNAL);
- private static final String SORT_ORDER = Media.DATE_MODIFIED + " DESC";
- private static final String IMAGE_SELECTION = createSelection(
- MessagePartData.ACCEPTABLE_IMAGE_TYPES,
- new Integer[] { FileColumns.MEDIA_TYPE_IMAGE });
-
- public GalleryBoundCursorLoader(final String bindingId, final Context context) {
- super(bindingId, context, STORAGE_URI, GalleryGridItemData.IMAGE_PROJECTION,
- IMAGE_SELECTION, null, SORT_ORDER);
- }
-
- private static String createSelection(final String[] mimeTypes, Integer[] mediaTypes) {
- return Media.MIME_TYPE + " IN ('" + Joiner.on("','").join(mimeTypes) + "') AND "
- + FileColumns.MEDIA_TYPE + " IN (" + Joiner.on(',').join(mediaTypes) + ")";
- }
-}
diff --git a/src/com/android/messaging/datamodel/MediaScratchFileProvider.java b/src/com/android/messaging/datamodel/MediaScratchFileProvider.java
deleted file mode 100644
index 29ae4f4..0000000
--- a/src/com/android/messaging/datamodel/MediaScratchFileProvider.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MatrixCursor.RowBuilder;
-import android.net.Uri;
-import android.provider.OpenableColumns;
-import android.support.v4.util.SimpleArrayMap;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * A very simple content provider that can serve media files from our cache directory.
- */
-public class MediaScratchFileProvider extends FileProvider {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap =
- new SimpleArrayMap<Uri, String>();
-
- @VisibleForTesting
- public static final String AUTHORITY =
- "com.android.messaging.datamodel.MediaScratchFileProvider";
- private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace";
-
- public static boolean isMediaScratchSpaceUri(final Uri uri) {
- if (uri == null) {
- return false;
- }
-
- final List<String> segments = uri.getPathSegments();
- return (TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_CONTENT) &&
- TextUtils.equals(uri.getAuthority(), AUTHORITY) &&
- segments.size() == 1 && FileProvider.isValidFileId(segments.get(0)));
- }
-
- /**
- * Returns a uri that can be used to access a raw mms file.
- *
- * @return the URI for an raw mms file
- */
- public static Uri buildMediaScratchSpaceUri(final String extension) {
- final Uri uri = FileProvider.buildFileUri(AUTHORITY, extension);
- final File file = getFileWithExtension(uri.getPath(), extension);
- if (!ensureFileExists(file)) {
- LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath());
- }
- return uri;
- }
-
- public static File getFileFromUri(final Uri uri) {
- Assert.equals(AUTHORITY, uri.getAuthority());
- return getFileWithExtension(uri.getPath(), getExtensionFromUri(uri));
- }
-
- public static Uri.Builder getUriBuilder() {
- return (new Uri.Builder()).authority(AUTHORITY).scheme(ContentResolver.SCHEME_CONTENT);
- }
-
- @Override
- File getFile(final String path, final String extension) {
- return getFileWithExtension(path, extension);
- }
-
- private static File getFileWithExtension(final String path, final String extension) {
- final Context context = Factory.get().getApplicationContext();
- return new File(getDirectory(context),
- TextUtils.isEmpty(extension) ? path : path + "." + extension);
- }
-
- private static File getDirectory(final Context context) {
- return new File(context.getCacheDir(), MEDIA_SCRATCH_SPACE_DIR);
- }
-
- @Override
- public Cursor query(final Uri uri, final String[] projection, final String selection,
- final String[] selectionArgs, final String sortOrder) {
- if (projection != null && projection.length > 0 &&
- TextUtils.equals(projection[0], OpenableColumns.DISPLAY_NAME) &&
- isMediaScratchSpaceUri(uri)) {
- // Retrieve the display name associated with a temp file. This is used by the Contacts
- // ImportVCardActivity to retrieve the name of the contact(s) being imported.
- String displayName;
- synchronized (sUriToDisplayNameMap) {
- displayName = sUriToDisplayNameMap.get(uri);
- }
- if (!TextUtils.isEmpty(displayName)) {
- MatrixCursor cursor =
- new MatrixCursor(new String[] { OpenableColumns.DISPLAY_NAME });
- RowBuilder row = cursor.newRow();
- row.add(displayName);
- return cursor;
- }
- }
- return null;
- }
-
- public static void addUriToDisplayNameEntry(final Uri scratchFileUri,
- final String displayName) {
- if (TextUtils.isEmpty(displayName)) {
- return;
- }
- synchronized (sUriToDisplayNameMap) {
- sUriToDisplayNameMap.put(scratchFileUri, displayName);
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/MemoryCacheManager.java b/src/com/android/messaging/datamodel/MemoryCacheManager.java
deleted file mode 100644
index 0968cff..0000000
--- a/src/com/android/messaging/datamodel/MemoryCacheManager.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import com.android.messaging.Factory;
-
-import java.util.HashSet;
-
-/**
- * Utility abstraction which allows MemoryCaches in an application to register and then when there
- * is memory pressure provide a callback to reclaim the memory in the caches.
- */
-public class MemoryCacheManager {
- private final HashSet<MemoryCache> mMemoryCaches = new HashSet<MemoryCache>();
- private final Object mMemoryCacheLock = new Object();
-
- public static MemoryCacheManager get() {
- return Factory.get().getMemoryCacheManager();
- }
-
- /**
- * Extend this interface to provide a reclaim method on a memory cache.
- */
- public interface MemoryCache {
- void reclaim();
- }
-
- /**
- * Register the memory cache with the application.
- */
- public void registerMemoryCache(final MemoryCache cache) {
- synchronized (mMemoryCacheLock) {
- mMemoryCaches.add(cache);
- }
- }
-
- /**
- * Unregister the memory cache with the application.
- */
- public void unregisterMemoryCache(final MemoryCache cache) {
- synchronized (mMemoryCacheLock) {
- mMemoryCaches.remove(cache);
- }
- }
-
- /**
- * Reclaim memory in all the memory caches in the application.
- */
- @SuppressWarnings("unchecked")
- public void reclaimMemory() {
- // We're creating a cache copy in the lock to ensure we're not working on a concurrently
- // modified set, then reclaim outside of the lock to minimize the time within the lock.
- final HashSet<MemoryCache> shallowCopy;
- synchronized (mMemoryCacheLock) {
- shallowCopy = (HashSet<MemoryCache>) mMemoryCaches.clone();
- }
- for (final MemoryCache cache : shallowCopy) {
- cache.reclaim();
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/MessageNotificationState.java b/src/com/android/messaging/datamodel/MessageNotificationState.java
deleted file mode 100644
index 0bd4aaa..0000000
--- a/src/com/android/messaging/datamodel/MessageNotificationState.java
+++ /dev/null
@@ -1,1342 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.Builder;
-import android.support.v4.app.NotificationCompat.WearableExtender;
-import android.support.v4.app.NotificationManagerCompat;
-import android.text.Html;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
-import android.text.style.TextAppearanceSpan;
-import android.text.style.URLSpan;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.ConversationParticipantsData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.VideoThumbnailRequest;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ConversationIdSet;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PendingIntentConstants;
-import com.android.messaging.util.UriUtil;
-import com.google.common.collect.Lists;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Notification building class for conversation messages.
- *
- * Message Notifications are built in several stages with several utility classes.
- * 1) Perform a database query and fill a data structure with information on messages and
- * conversations which need to be notified.
- * 2) Based on the data structure choose an appropriate NotificationState subclass to
- * represent all the notifications.
- * -- For one or more messages in one conversation: MultiMessageNotificationState.
- * -- For multiple messages in multiple conversations: MultiConversationNotificationState
- *
- * A three level structure is used to coalesce the data from the database. From bottom to top:
- * 1) NotificationLineInfo - A single message that needs to be notified.
- * 2) ConversationLineInfo - A list of NotificationLineInfo in a single conversation.
- * 3) ConversationInfoList - A list of ConversationLineInfo and the total number of messages.
- *
- * The createConversationInfoList function performs the query and creates the data structure.
- */
-public abstract class MessageNotificationState extends NotificationState {
- // Logging
- static final String TAG = LogUtil.BUGLE_NOTIFICATIONS_TAG;
- private static final int MAX_MESSAGES_IN_WEARABLE_PAGE = 20;
-
- private static final int MAX_CHARACTERS_IN_GROUP_NAME = 30;
-
- private static final int REPLY_INTENT_REQUEST_CODE_OFFSET = 0;
- private static final int NUM_EXTRA_REQUEST_CODES_NEEDED = 1;
- protected String mTickerSender = null;
- protected CharSequence mTickerText = null;
- protected String mTitle = null;
- protected CharSequence mContent = null;
- protected Uri mAttachmentUri = null;
- protected String mAttachmentType = null;
- protected boolean mTickerNoContent;
-
- @Override
- protected Uri getAttachmentUri() {
- return mAttachmentUri;
- }
-
- @Override
- protected String getAttachmentType() {
- return mAttachmentType;
- }
-
- @Override
- public int getIcon() {
- return R.drawable.ic_sms_light;
- }
-
- @Override
- public int getPriority() {
- // Returning PRIORITY_HIGH causes L to put up a HUD notification. Without it, the ticker
- // isn't displayed.
- return Notification.PRIORITY_HIGH;
- }
-
- /**
- * Base class for single notification events for messages. Multiple of these
- * may be grouped into a single conversation.
- */
- static class NotificationLineInfo {
-
- final int mNotificationType;
-
- NotificationLineInfo() {
- mNotificationType = BugleNotifications.LOCAL_SMS_NOTIFICATION;
- }
-
- NotificationLineInfo(final int notificationType) {
- mNotificationType = notificationType;
- }
- }
-
- /**
- * Information on a single chat message which should be shown in a notification.
- */
- static class MessageLineInfo extends NotificationLineInfo {
- final CharSequence mText;
- Uri mAttachmentUri;
- String mAttachmentType;
- final String mAuthorFullName;
- final String mAuthorFirstName;
- boolean mIsManualDownloadNeeded;
- final String mMessageId;
-
- MessageLineInfo(final boolean isGroup, final String authorFullName,
- final String authorFirstName, final CharSequence text, final Uri attachmentUrl,
- final String attachmentType, final boolean isManualDownloadNeeded,
- final String messageId) {
- super(BugleNotifications.LOCAL_SMS_NOTIFICATION);
- mAuthorFullName = authorFullName;
- mAuthorFirstName = authorFirstName;
- mText = text;
- mAttachmentUri = attachmentUrl;
- mAttachmentType = attachmentType;
- mIsManualDownloadNeeded = isManualDownloadNeeded;
- mMessageId = messageId;
- }
- }
-
- /**
- * Information on all the notification messages within a single conversation.
- */
- static class ConversationLineInfo {
- // Conversation id of the latest message in the notification for this merged conversation.
- final String mConversationId;
-
- // True if this represents a group conversation.
- final boolean mIsGroup;
-
- // Name of the group conversation if available.
- final String mGroupConversationName;
-
- // True if this conversation's recipients includes one or more email address(es)
- // (see ConversationColumns.INCLUDE_EMAIL_ADDRESS)
- final boolean mIncludeEmailAddress;
-
- // Timestamp of the latest message
- final long mReceivedTimestamp;
-
- // Self participant id.
- final String mSelfParticipantId;
-
- // List of individual line notifications to be parsed later.
- final List<NotificationLineInfo> mLineInfos;
-
- // Total number of messages. Might be different that mLineInfos.size() as the number of
- // line infos is capped.
- int mTotalMessageCount;
-
- // Custom ringtone if set
- final String mRingtoneUri;
-
- // Should notification be enabled for this conversation?
- final boolean mNotificationEnabled;
-
- // Should notifications vibrate for this conversation?
- final boolean mNotificationVibrate;
-
- // Avatar uri of sender
- final Uri mAvatarUri;
-
- // Contact uri of sender
- final Uri mContactUri;
-
- // Subscription id.
- final int mSubId;
-
- // Number of participants
- final int mParticipantCount;
-
- public ConversationLineInfo(final String conversationId,
- final boolean isGroup,
- final String groupConversationName,
- final boolean includeEmailAddress,
- final long receivedTimestamp,
- final String selfParticipantId,
- final String ringtoneUri,
- final boolean notificationEnabled,
- final boolean notificationVibrate,
- final Uri avatarUri,
- final Uri contactUri,
- final int subId,
- final int participantCount) {
- mConversationId = conversationId;
- mIsGroup = isGroup;
- mGroupConversationName = groupConversationName;
- mIncludeEmailAddress = includeEmailAddress;
- mReceivedTimestamp = receivedTimestamp;
- mSelfParticipantId = selfParticipantId;
- mLineInfos = new ArrayList<NotificationLineInfo>();
- mTotalMessageCount = 0;
- mRingtoneUri = ringtoneUri;
- mAvatarUri = avatarUri;
- mContactUri = contactUri;
- mNotificationEnabled = notificationEnabled;
- mNotificationVibrate = notificationVibrate;
- mSubId = subId;
- mParticipantCount = participantCount;
- }
-
- public int getLatestMessageNotificationType() {
- final MessageLineInfo messageLineInfo = getLatestMessageLineInfo();
- if (messageLineInfo == null) {
- return BugleNotifications.LOCAL_SMS_NOTIFICATION;
- }
- return messageLineInfo.mNotificationType;
- }
-
- public String getLatestMessageId() {
- final MessageLineInfo messageLineInfo = getLatestMessageLineInfo();
- if (messageLineInfo == null) {
- return null;
- }
- return messageLineInfo.mMessageId;
- }
-
- public boolean getDoesLatestMessageNeedDownload() {
- final MessageLineInfo messageLineInfo = getLatestMessageLineInfo();
- if (messageLineInfo == null) {
- return false;
- }
- return messageLineInfo.mIsManualDownloadNeeded;
- }
-
- private MessageLineInfo getLatestMessageLineInfo() {
- // The latest message is stored at index zero of the message line infos.
- if (mLineInfos.size() > 0 && mLineInfos.get(0) instanceof MessageLineInfo) {
- return (MessageLineInfo) mLineInfos.get(0);
- }
- return null;
- }
- }
-
- /**
- * Information on all the notification messages across all conversations.
- */
- public static class ConversationInfoList {
- final int mMessageCount;
- final List<ConversationLineInfo> mConvInfos;
- public ConversationInfoList(final int count, final List<ConversationLineInfo> infos) {
- mMessageCount = count;
- mConvInfos = infos;
- }
- }
-
- final ConversationInfoList mConvList;
- private long mLatestReceivedTimestamp;
-
- private static ConversationIdSet makeConversationIdSet(final ConversationInfoList convList) {
- ConversationIdSet set = null;
- if (convList != null && convList.mConvInfos != null && convList.mConvInfos.size() > 0) {
- set = new ConversationIdSet();
- for (final ConversationLineInfo info : convList.mConvInfos) {
- set.add(info.mConversationId);
- }
- }
- return set;
- }
-
- protected MessageNotificationState(final ConversationInfoList convList) {
- super(makeConversationIdSet(convList));
- mConvList = convList;
- mType = PendingIntentConstants.SMS_NOTIFICATION_ID;
- mLatestReceivedTimestamp = Long.MIN_VALUE;
- if (convList != null) {
- for (final ConversationLineInfo info : convList.mConvInfos) {
- mLatestReceivedTimestamp = Math.max(mLatestReceivedTimestamp,
- info.mReceivedTimestamp);
- }
- }
- }
-
- @Override
- public long getLatestReceivedTimestamp() {
- return mLatestReceivedTimestamp;
- }
-
- @Override
- public int getNumRequestCodesNeeded() {
- // Get additional request codes for the Reply PendingIntent (wearables only)
- // and the DND PendingIntent.
- return super.getNumRequestCodesNeeded() + NUM_EXTRA_REQUEST_CODES_NEEDED;
- }
-
- private int getBaseExtraRequestCode() {
- return mBaseRequestCode + super.getNumRequestCodesNeeded();
- }
-
- public int getReplyIntentRequestCode() {
- return getBaseExtraRequestCode() + REPLY_INTENT_REQUEST_CODE_OFFSET;
- }
-
- @Override
- public PendingIntent getClearIntent() {
- return UIIntents.get().getPendingIntentForClearingNotifications(
- Factory.get().getApplicationContext(),
- BugleNotifications.UPDATE_MESSAGES,
- mConversationIds,
- getClearIntentRequestCode());
- }
-
- /**
- * Notification for multiple messages in at least 2 different conversations.
- */
- public static class MultiConversationNotificationState extends MessageNotificationState {
-
- public final List<MessageNotificationState>
- mChildren = new ArrayList<MessageNotificationState>();
-
- public MultiConversationNotificationState(
- final ConversationInfoList convList, final MessageNotificationState state) {
- super(convList);
- mAttachmentUri = null;
- mAttachmentType = null;
-
- // Pull the ticker title/text from the single notification
- mTickerSender = state.getTitle();
- mTitle = Factory.get().getApplicationContext().getResources().getQuantityString(
- R.plurals.notification_new_messages,
- convList.mMessageCount, convList.mMessageCount);
- mTickerText = state.mContent;
-
- // Create child notifications for each conversation,
- // which will be displayed (only) on a wearable device.
- for (int i = 0; i < convList.mConvInfos.size(); i++) {
- final ConversationLineInfo convInfo = convList.mConvInfos.get(i);
- if (!(convInfo.mLineInfos.get(0) instanceof MessageLineInfo)) {
- continue;
- }
- setPeopleForConversation(convInfo.mConversationId);
- final ConversationInfoList list = new ConversationInfoList(
- convInfo.mTotalMessageCount, Lists.newArrayList(convInfo));
- mChildren.add(new BundledMessageNotificationState(list, i));
- }
- }
-
- @Override
- public int getIcon() {
- return R.drawable.ic_sms_multi_light;
- }
-
- @Override
- protected NotificationCompat.Style build(final Builder builder) {
- builder.setContentTitle(mTitle);
- NotificationCompat.InboxStyle inboxStyle = null;
- inboxStyle = new NotificationCompat.InboxStyle(builder);
-
- final Context context = Factory.get().getApplicationContext();
- // enumeration_comma is defined as ", "
- final String separator = context.getString(R.string.enumeration_comma);
- final StringBuilder senders = new StringBuilder();
- long when = 0;
- for (int i = 0; i < mConvList.mConvInfos.size(); i++) {
- final ConversationLineInfo convInfo = mConvList.mConvInfos.get(i);
- if (convInfo.mReceivedTimestamp > when) {
- when = convInfo.mReceivedTimestamp;
- }
- String sender;
- CharSequence text;
- final NotificationLineInfo lineInfo = convInfo.mLineInfos.get(0);
- final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfo;
- if (convInfo.mIsGroup) {
- sender = (convInfo.mGroupConversationName.length() >
- MAX_CHARACTERS_IN_GROUP_NAME) ?
- truncateGroupMessageName(convInfo.mGroupConversationName)
- : convInfo.mGroupConversationName;
- } else {
- sender = messageLineInfo.mAuthorFullName;
- }
- text = messageLineInfo.mText;
- mAttachmentUri = messageLineInfo.mAttachmentUri;
- mAttachmentType = messageLineInfo.mAttachmentType;
-
- inboxStyle.addLine(BugleNotifications.formatInboxMessage(
- sender, text, mAttachmentUri, mAttachmentType));
- if (sender != null) {
- if (senders.length() > 0) {
- senders.append(separator);
- }
- senders.append(sender);
- }
- }
- // for collapsed state
- mContent = senders;
- builder.setContentText(senders)
- .setTicker(getTicker())
- .setWhen(when);
-
- return inboxStyle;
- }
- }
-
- /**
- * Truncate group conversation name to be displayed in the notifications. This either truncates
- * the entire group name or finds the last comma in the available length and truncates the name
- * at that point
- */
- private static String truncateGroupMessageName(final String conversationName) {
- int endIndex = MAX_CHARACTERS_IN_GROUP_NAME;
- for (int i = MAX_CHARACTERS_IN_GROUP_NAME; i >= 0; i--) {
- // The dividing marker should stay consistent with ConversationListItemData.DIVIDER_TEXT
- if (conversationName.charAt(i) == ',') {
- endIndex = i;
- break;
- }
- }
- return conversationName.substring(0, endIndex) + '\u2026';
- }
-
- /**
- * Notification for multiple messages in a single conversation. Also used if there is a single
- * message in a single conversation.
- */
- public static class MultiMessageNotificationState extends MessageNotificationState {
-
- public MultiMessageNotificationState(final ConversationInfoList convList) {
- super(convList);
- // This conversation has been accepted.
- final ConversationLineInfo convInfo = convList.mConvInfos.get(0);
- setAvatarUrlsForConversation(convInfo.mConversationId);
- setPeopleForConversation(convInfo.mConversationId);
-
- final Context context = Factory.get().getApplicationContext();
- MessageLineInfo messageInfo = (MessageLineInfo) convInfo.mLineInfos.get(0);
- // attached photo
- mAttachmentUri = messageInfo.mAttachmentUri;
- mAttachmentType = messageInfo.mAttachmentType;
- mContent = messageInfo.mText;
-
- if (mAttachmentUri != null) {
- // The default attachment type is an image, since that's what was originally
- // supported. When there's no content type, assume it's an image.
- int message = R.string.notification_picture;
- if (ContentType.isAudioType(mAttachmentType)) {
- message = R.string.notification_audio;
- } else if (ContentType.isVideoType(mAttachmentType)) {
- message = R.string.notification_video;
- } else if (ContentType.isVCardType(mAttachmentType)) {
- message = R.string.notification_vcard;
- }
- final String attachment = context.getString(message);
- final SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
- if (!TextUtils.isEmpty(mContent)) {
- spanBuilder.append(mContent).append(System.getProperty("line.separator"));
- }
- final int start = spanBuilder.length();
- spanBuilder.append(attachment);
- spanBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, spanBuilder.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- mContent = spanBuilder;
- }
- if (convInfo.mIsGroup) {
- // When the message is part of a group, the sender's first name
- // is prepended to the message, but not for the ticker message.
- mTickerText = mContent;
- mTickerSender = messageInfo.mAuthorFullName;
- // append the bold name to the front of the message
- mContent = BugleNotifications.buildSpaceSeparatedMessage(
- messageInfo.mAuthorFullName, mContent, mAttachmentUri,
- mAttachmentType);
- mTitle = convInfo.mGroupConversationName;
- } else {
- // No matter how many messages there are, since this is a 1:1, just
- // get the author full name from the first one.
- messageInfo = (MessageLineInfo) convInfo.mLineInfos.get(0);
- mTitle = messageInfo.mAuthorFullName;
- }
- }
-
- @Override
- protected NotificationCompat.Style build(final Builder builder) {
- builder.setContentTitle(mTitle)
- .setTicker(getTicker());
-
- NotificationCompat.Style notifStyle = null;
- final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
- final List<NotificationLineInfo> lineInfos = convInfo.mLineInfos;
- final int messageCount = lineInfos.size();
- // At this point, all the messages come from the same conversation. We need to load
- // the sender's avatar and then finish building the notification on a callback.
-
- builder.setContentText(mContent); // for collapsed state
-
- if (messageCount == 1) {
- final boolean shouldShowImage = ContentType.isImageType(mAttachmentType)
- || (ContentType.isVideoType(mAttachmentType)
- && VideoThumbnailRequest.shouldShowIncomingVideoThumbnails());
- if (mAttachmentUri != null && shouldShowImage) {
- // Show "Picture" as the content
- final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfos.get(0);
- String authorFirstName = messageLineInfo.mAuthorFirstName;
-
- // For the collapsed state, just show "picture" unless this is a
- // group conversation. If it's a group, show the sender name and
- // "picture".
- final CharSequence tickerTag =
- BugleNotifications.formatAttachmentTag(authorFirstName,
- mAttachmentType);
- // For 1:1 notifications don't show first name in the notification, but
- // do show it in the ticker text
- CharSequence pictureTag = tickerTag;
- if (!convInfo.mIsGroup) {
- authorFirstName = null;
- pictureTag = BugleNotifications.formatAttachmentTag(authorFirstName,
- mAttachmentType);
- }
- builder.setContentText(pictureTag);
- builder.setTicker(tickerTag);
-
- notifStyle = new NotificationCompat.BigPictureStyle(builder)
- .setSummaryText(BugleNotifications.formatInboxMessage(
- authorFirstName,
- null, null,
- null)); // expanded state, just show sender
- } else {
- notifStyle = new NotificationCompat.BigTextStyle(builder)
- .bigText(mContent);
- }
- } else {
- // We've got multiple messages for the same sender.
- // Starting with the oldest new message, display the full text of each message.
- // Begin a line for each subsequent message.
- final SpannableStringBuilder buf = new SpannableStringBuilder();
-
- for (int i = lineInfos.size() - 1; i >= 0; --i) {
- final NotificationLineInfo info = lineInfos.get(i);
- final MessageLineInfo messageLineInfo = (MessageLineInfo) info;
- mAttachmentUri = messageLineInfo.mAttachmentUri;
- mAttachmentType = messageLineInfo.mAttachmentType;
- CharSequence text = messageLineInfo.mText;
- if (!TextUtils.isEmpty(text) || mAttachmentUri != null) {
- if (convInfo.mIsGroup) {
- // append the bold name to the front of the message
- text = BugleNotifications.buildSpaceSeparatedMessage(
- messageLineInfo.mAuthorFullName, text, mAttachmentUri,
- mAttachmentType);
- } else {
- text = BugleNotifications.buildSpaceSeparatedMessage(
- null, text, mAttachmentUri, mAttachmentType);
- }
- buf.append(text);
- if (i > 0) {
- buf.append('\n');
- }
- }
- }
-
- // Show a single notification -- big style with the text of all the messages
- notifStyle = new NotificationCompat.BigTextStyle(builder).bigText(buf);
- }
- builder.setWhen(convInfo.mReceivedTimestamp);
- return notifStyle;
- }
-
- }
-
- private static boolean firstNameUsedMoreThanOnce(
- final HashMap<String, Integer> map, final String firstName) {
- if (map == null) {
- return false;
- }
- if (firstName == null) {
- return false;
- }
- final Integer count = map.get(firstName);
- if (count != null) {
- return count > 1;
- } else {
- return false;
- }
- }
-
- private static HashMap<String, Integer> scanFirstNames(final String conversationId) {
- final Context context = Factory.get().getApplicationContext();
- final Uri uri =
- MessagingContentProvider.buildConversationParticipantsUri(conversationId);
- final Cursor participantsCursor = context.getContentResolver().query(
- uri, ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
- final ConversationParticipantsData participantsData = new ConversationParticipantsData();
- participantsData.bind(participantsCursor);
- final Iterator<ParticipantData> iter = participantsData.iterator();
-
- final HashMap<String, Integer> firstNames = new HashMap<String, Integer>();
- boolean seenSelf = false;
- while (iter.hasNext()) {
- final ParticipantData participant = iter.next();
- // Make sure we only add the self participant once
- if (participant.isSelf()) {
- if (seenSelf) {
- continue;
- } else {
- seenSelf = true;
- }
- }
-
- final String firstName = participant.getFirstName();
- if (firstName == null) {
- continue;
- }
-
- final int currentCount = firstNames.containsKey(firstName)
- ? firstNames.get(firstName)
- : 0;
- firstNames.put(firstName, currentCount + 1);
- }
- return firstNames;
- }
-
- // Essentially, we're building a list of the past 20 messages for this conversation to display
- // on the wearable.
- public static Notification buildConversationPageForWearable(final String conversationId,
- int participantCount) {
- final Context context = Factory.get().getApplicationContext();
-
- // Limit the number of messages to show. We just want enough to provide context for the
- // notification. Fetch one more than we need, so we can tell if there are more messages
- // before the one we're showing.
- // TODO: in the query, a multipart message will contain a row for each part.
- // We might need a smarter GROUP_BY. On the other hand, we might want to show each of the
- // parts as separate messages on the wearable.
- final int limit = MAX_MESSAGES_IN_WEARABLE_PAGE + 1;
-
- final List<CharSequence> messages = Lists.newArrayList();
- boolean hasSeenMessagesBeforeNotification = false;
- Cursor convMessageCursor = null;
- try {
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- final String[] queryArgs = { conversationId };
- final String convPageSql = ConversationMessageData.getWearableQuerySql() + " LIMIT " +
- limit;
- convMessageCursor = db.rawQuery(
- convPageSql,
- queryArgs);
-
- if (convMessageCursor == null || !convMessageCursor.moveToFirst()) {
- return null;
- }
- final ConversationMessageData convMessageData =
- new ConversationMessageData();
-
- final HashMap<String, Integer> firstNames = scanFirstNames(conversationId);
- do {
- convMessageData.bind(convMessageCursor);
-
- final String authorFullName = convMessageData.getSenderFullName();
- final String authorFirstName = convMessageData.getSenderFirstName();
- String text = convMessageData.getText();
-
- final boolean isSmsPushNotification = convMessageData.getIsMmsNotification();
-
- // if auto-download was off to show a message to tap to download the message. We
- // might need to get that working again.
- if (isSmsPushNotification && text != null) {
- text = convertHtmlAndStripUrls(text).toString();
- }
- // Skip messages without any content
- if (TextUtils.isEmpty(text) && !convMessageData.hasAttachments()) {
- continue;
- }
- // Track whether there are messages prior to the one(s) shown in the notification.
- if (convMessageData.getIsSeen()) {
- hasSeenMessagesBeforeNotification = true;
- }
-
- final boolean usedMoreThanOnce = firstNameUsedMoreThanOnce(
- firstNames, authorFirstName);
- String displayName = usedMoreThanOnce ? authorFullName : authorFirstName;
- if (TextUtils.isEmpty(displayName)) {
- if (convMessageData.getIsIncoming()) {
- displayName = convMessageData.getSenderDisplayDestination();
- if (TextUtils.isEmpty(displayName)) {
- displayName = context.getString(R.string.unknown_sender);
- }
- } else {
- displayName = context.getString(R.string.unknown_self_participant);
- }
- }
-
- Uri attachmentUri = null;
- String attachmentType = null;
- final List<MessagePartData> attachments = convMessageData.getAttachments();
- for (final MessagePartData messagePartData : attachments) {
- // Look for the first attachment that's not the text piece.
- if (!messagePartData.isText()) {
- attachmentUri = messagePartData.getContentUri();
- attachmentType = messagePartData.getContentType();
- break;
- }
- }
-
- final CharSequence message = BugleNotifications.buildSpaceSeparatedMessage(
- displayName, text, attachmentUri, attachmentType);
- messages.add(message);
-
- } while (convMessageCursor.moveToNext());
- } finally {
- if (convMessageCursor != null) {
- convMessageCursor.close();
- }
- }
-
- // If there is no conversation history prior to what is already visible in the main
- // notification, there's no need to include the conversation log, too.
- final int maxMessagesInNotification = getMaxMessagesInConversationNotification();
- if (!hasSeenMessagesBeforeNotification && messages.size() <= maxMessagesInNotification) {
- return null;
- }
-
- final SpannableStringBuilder bigText = new SpannableStringBuilder();
- // There is at least 1 message prior to the first one that we're going to show.
- // Indicate this by inserting an ellipsis at the beginning of the conversation log.
- if (convMessageCursor.getCount() == limit) {
- bigText.append(context.getString(R.string.ellipsis) + "\n\n");
- if (messages.size() > MAX_MESSAGES_IN_WEARABLE_PAGE) {
- messages.remove(messages.size() - 1);
- }
- }
- // Messages are sorted in descending timestamp order, so iterate backwards
- // to get them back in ascending order for display purposes.
- for (int i = messages.size() - 1; i >= 0; --i) {
- bigText.append(messages.get(i));
- if (i > 0) {
- bigText.append("\n\n");
- }
- }
- ++participantCount; // Add in myself
-
- if (participantCount > 2) {
- final SpannableString statusText = new SpannableString(
- context.getResources().getQuantityString(R.plurals.wearable_participant_count,
- participantCount, participantCount));
- statusText.setSpan(new ForegroundColorSpan(context.getResources().getColor(
- R.color.wearable_notification_participants_count)), 0, statusText.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- bigText.append("\n\n").append(statusText);
- }
-
- final NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context);
- final NotificationCompat.Style notifStyle =
- new NotificationCompat.BigTextStyle(notifBuilder).bigText(bigText);
- notifBuilder.setStyle(notifStyle);
-
- final WearableExtender wearableExtender = new WearableExtender();
- wearableExtender.setStartScrollBottom(true);
- notifBuilder.extend(wearableExtender);
-
- return notifBuilder.build();
- }
-
- /**
- * Notification for one or more messages in a single conversation, which is bundled together
- * with notifications for other conversations on a wearable device.
- */
- public static class BundledMessageNotificationState extends MultiMessageNotificationState {
- public int mGroupOrder;
- public BundledMessageNotificationState(final ConversationInfoList convList,
- final int groupOrder) {
- super(convList);
- mGroupOrder = groupOrder;
- }
- }
-
- /**
- * Performs a query on the database.
- */
- private static ConversationInfoList createConversationInfoList() {
- // Map key is conversation id. We use LinkedHashMap to ensure that entries are iterated in
- // the same order they were originally added. We scan unseen messages from newest to oldest,
- // so the corresponding conversations are added in that order, too.
- final Map<String, ConversationLineInfo> convLineInfos = new LinkedHashMap<>();
- int messageCount = 0;
-
- Cursor convMessageCursor = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- convMessageCursor = db.rawQuery(
- ConversationMessageData.getNotificationQuerySql(),
- null);
-
- if (convMessageCursor != null && convMessageCursor.moveToFirst()) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "MessageNotificationState: Found unseen message notifications.");
- }
- final ConversationMessageData convMessageData =
- new ConversationMessageData();
-
- HashMap<String, Integer> firstNames = null;
- String conversationIdForFirstNames = null;
- String groupConversationName = null;
- final int maxMessages = getMaxMessagesInConversationNotification();
-
- do {
- convMessageData.bind(convMessageCursor);
-
- // First figure out if this is a valid message.
- String authorFullName = convMessageData.getSenderFullName();
- String authorFirstName = convMessageData.getSenderFirstName();
- final String messageText = convMessageData.getText();
-
- final String convId = convMessageData.getConversationId();
- final String messageId = convMessageData.getMessageId();
-
- CharSequence text = messageText;
- final boolean isManualDownloadNeeded = convMessageData.getIsMmsNotification();
- if (isManualDownloadNeeded) {
- // Don't try and convert the text from html if it's sms and not a sms push
- // notification.
- Assert.equals(MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD,
- convMessageData.getStatus());
- text = context.getResources().getString(
- R.string.message_title_manual_download);
- }
- ConversationLineInfo currConvInfo = convLineInfos.get(convId);
- if (currConvInfo == null) {
- final ConversationListItemData convData =
- ConversationListItemData.getExistingConversation(db, convId);
- if (!convData.getNotificationEnabled()) {
- // Skip conversations that have notifications disabled.
- continue;
- }
- final int subId = BugleDatabaseOperations.getSelfSubscriptionId(db,
- convData.getSelfId());
- groupConversationName = convData.getName();
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- convMessageData.getSenderProfilePhotoUri(),
- convMessageData.getSenderFullName(),
- convMessageData.getSenderNormalizedDestination(),
- convMessageData.getSenderContactLookupKey());
- currConvInfo = new ConversationLineInfo(convId,
- convData.getIsGroup(),
- groupConversationName,
- convData.getIncludeEmailAddress(),
- convMessageData.getReceivedTimeStamp(),
- convData.getSelfId(),
- convData.getNotificationSoundUri(),
- convData.getNotificationEnabled(),
- convData.getNotifiationVibrate(),
- avatarUri,
- convMessageData.getSenderContactLookupUri(),
- subId,
- convData.getParticipantCount());
- convLineInfos.put(convId, currConvInfo);
- }
- // Prepare the message line
- if (currConvInfo.mTotalMessageCount < maxMessages) {
- if (currConvInfo.mIsGroup) {
- if (authorFirstName == null) {
- // authorFullName might be null as well. In that case, we won't
- // show an author. That is better than showing all the group
- // names again on the 2nd line.
- authorFirstName = authorFullName;
- }
- } else {
- // don't recompute this if we don't need to
- if (!TextUtils.equals(conversationIdForFirstNames, convId)) {
- firstNames = scanFirstNames(convId);
- conversationIdForFirstNames = convId;
- }
- if (firstNames != null) {
- final Integer count = firstNames.get(authorFirstName);
- if (count != null && count > 1) {
- authorFirstName = authorFullName;
- }
- }
-
- if (authorFullName == null) {
- authorFullName = groupConversationName;
- }
- if (authorFirstName == null) {
- authorFirstName = groupConversationName;
- }
- }
- final String subjectText = MmsUtils.cleanseMmsSubject(
- context.getResources(),
- convMessageData.getMmsSubject());
- if (!TextUtils.isEmpty(subjectText)) {
- final String subjectLabel =
- context.getString(R.string.subject_label);
- final SpannableStringBuilder spanBuilder =
- new SpannableStringBuilder();
-
- spanBuilder.append(context.getString(R.string.notification_subject,
- subjectLabel, subjectText));
- spanBuilder.setSpan(new TextAppearanceSpan(
- context, R.style.NotificationSubjectText), 0,
- subjectLabel.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- if (!TextUtils.isEmpty(text)) {
- // Now add the actual message text below the subject header.
- spanBuilder.append(System.getProperty("line.separator") + text);
- }
- text = spanBuilder;
- }
- // If we've got attachments, find the best one. If one of the messages is
- // a photo, save the url so we'll display a big picture notification.
- // Otherwise, show the first one we find.
- Uri attachmentUri = null;
- String attachmentType = null;
- final MessagePartData messagePartData =
- getMostInterestingAttachment(convMessageData);
- if (messagePartData != null) {
- attachmentUri = messagePartData.getContentUri();
- attachmentType = messagePartData.getContentType();
- }
- currConvInfo.mLineInfos.add(new MessageLineInfo(currConvInfo.mIsGroup,
- authorFullName, authorFirstName, text,
- attachmentUri, attachmentType, isManualDownloadNeeded, messageId));
- }
- messageCount++;
- currConvInfo.mTotalMessageCount++;
- } while (convMessageCursor.moveToNext());
- }
- } finally {
- if (convMessageCursor != null) {
- convMessageCursor.close();
- }
- }
- if (convLineInfos.isEmpty()) {
- return null;
- } else {
- return new ConversationInfoList(messageCount,
- Lists.newLinkedList(convLineInfos.values()));
- }
- }
-
- /**
- * Scans all the attachments for a message and returns the most interesting one that we'll
- * show in a notification. By order of importance, in case there are multiple attachments:
- * 1- an image (because we can show the image as a BigPictureNotification)
- * 2- a video (because we can show a video frame as a BigPictureNotification)
- * 3- a vcard
- * 4- an audio attachment
- * @return MessagePartData for the most interesting part. Can be null.
- */
- private static MessagePartData getMostInterestingAttachment(
- final ConversationMessageData convMessageData) {
- final List<MessagePartData> attachments = convMessageData.getAttachments();
-
- MessagePartData imagePart = null;
- MessagePartData audioPart = null;
- MessagePartData vcardPart = null;
- MessagePartData videoPart = null;
-
- // 99.99% of the time there will be 0 or 1 part, since receiving slideshows is so
- // uncommon.
-
- // Remember the first of each type of part.
- for (final MessagePartData messagePartData : attachments) {
- if (messagePartData.isImage() && imagePart == null) {
- imagePart = messagePartData;
- }
- if (messagePartData.isVideo() && videoPart == null) {
- videoPart = messagePartData;
- }
- if (messagePartData.isVCard() && vcardPart == null) {
- vcardPart = messagePartData;
- }
- if (messagePartData.isAudio() && audioPart == null) {
- audioPart = messagePartData;
- }
- }
- if (imagePart != null) {
- return imagePart;
- } else if (videoPart != null) {
- return videoPart;
- } else if (audioPart != null) {
- return audioPart;
- } else if (vcardPart != null) {
- return vcardPart;
- }
- return null;
- }
-
- private static int getMaxMessagesInConversationNotification() {
- if (!BugleNotifications.isWearCompanionAppInstalled()) {
- return BugleGservices.get().getInt(
- BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION,
- BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_DEFAULT);
- }
- return BugleGservices.get().getInt(
- BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE,
- BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE_DEFAULT);
- }
-
- /**
- * Scans the database for messages that need to go into notifications. Creates the appropriate
- * MessageNotificationState depending on if there are multiple senders, or
- * messages from one sender.
- * @return NotificationState for the notification created.
- */
- public static NotificationState getNotificationState() {
- MessageNotificationState state = null;
- final ConversationInfoList convList = createConversationInfoList();
-
- if (convList == null || convList.mConvInfos.size() == 0) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "MessageNotificationState: No unseen notifications");
- }
- } else {
- final ConversationLineInfo convInfo = convList.mConvInfos.get(0);
- state = new MultiMessageNotificationState(convList);
-
- if (convList.mConvInfos.size() > 1) {
- // We've got notifications across multiple conversations. Pass in the notification
- // we just built of the most recent notification so we can use that to show the
- // user the new message in the ticker.
- state = new MultiConversationNotificationState(convList, state);
- } else {
- // For now, only show avatars for notifications for a single conversation.
- if (convInfo.mAvatarUri != null) {
- if (state.mParticipantAvatarsUris == null) {
- state.mParticipantAvatarsUris = new ArrayList<Uri>(1);
- }
- state.mParticipantAvatarsUris.add(convInfo.mAvatarUri);
- }
- if (convInfo.mContactUri != null) {
- if (state.mParticipantContactUris == null) {
- state.mParticipantContactUris = new ArrayList<Uri>(1);
- }
- state.mParticipantContactUris.add(convInfo.mContactUri);
- }
- }
- }
- if (state != null && LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "MessageNotificationState: Notification state created"
- + ", title = " + LogUtil.sanitizePII(state.mTitle)
- + ", content = " + LogUtil.sanitizePII(state.mContent.toString()));
- }
- return state;
- }
-
- protected String getTitle() {
- return mTitle;
- }
-
- @Override
- public int getLatestMessageNotificationType() {
- // This function is called to determine whether the most recent notification applies
- // to an sms conversation or a hangout conversation. We have different ringtone/vibrate
- // settings for both types of conversations.
- if (mConvList.mConvInfos.size() > 0) {
- final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
- return convInfo.getLatestMessageNotificationType();
- }
- return BugleNotifications.LOCAL_SMS_NOTIFICATION;
- }
-
- @Override
- public String getRingtoneUri() {
- if (mConvList.mConvInfos.size() > 0) {
- return mConvList.mConvInfos.get(0).mRingtoneUri;
- }
- return null;
- }
-
- @Override
- public boolean getNotificationVibrate() {
- if (mConvList.mConvInfos.size() > 0) {
- return mConvList.mConvInfos.get(0).mNotificationVibrate;
- }
- return false;
- }
-
- protected CharSequence getTicker() {
- return BugleNotifications.buildColonSeparatedMessage(
- mTickerSender != null ? mTickerSender : mTitle,
- mTickerText != null ? mTickerText : (mTickerNoContent ? null : mContent), null,
- null);
- }
-
- private static CharSequence convertHtmlAndStripUrls(final String s) {
- final Spanned text = Html.fromHtml(s);
- if (text instanceof Spannable) {
- stripUrls((Spannable) text);
- }
- return text;
- }
-
- // Since we don't want to show URLs in notifications, a function
- // to remove them in place.
- private static void stripUrls(final Spannable text) {
- final URLSpan[] spans = text.getSpans(0, text.length(), URLSpan.class);
- for (final URLSpan span : spans) {
- text.removeSpan(span);
- }
- }
-
- /*
- private static void updateAlertStatusMessages(final long thresholdDeltaMs) {
- // TODO may need this when supporting error notifications
- final EsDatabaseHelper helper = EsDatabaseHelper.getDatabaseHelper();
- final ContentValues values = new ContentValues();
- final long nowMicros = System.currentTimeMillis() * 1000;
- values.put(MessageColumns.ALERT_STATUS, "1");
- final String selection =
- MessageColumns.ALERT_STATUS + "=0 AND (" +
- MessageColumns.STATUS + "=" + EsProvider.MESSAGE_STATUS_FAILED_TO_SEND + " OR (" +
- MessageColumns.STATUS + "!=" + EsProvider.MESSAGE_STATUS_ON_SERVER + " AND " +
- MessageColumns.TIMESTAMP + "+" + thresholdDeltaMs*1000 + "<" + nowMicros + ")) ";
-
- final int updateCount = helper.getWritableDatabaseWrapper().update(
- EsProvider.MESSAGES_TABLE,
- values,
- selection,
- null);
- if (updateCount > 0) {
- EsConversationsData.notifyConversationsChanged();
- }
- }*/
-
- static CharSequence applyWarningTextColor(final Context context,
- final CharSequence text) {
- if (text == null) {
- return null;
- }
- final SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
- spanBuilder.append(text);
- spanBuilder.setSpan(new ForegroundColorSpan(context.getResources().getColor(
- R.color.notification_warning_color)), 0, text.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return spanBuilder;
- }
-
- /**
- * Check for failed messages and post notifications as needed.
- * TODO: Rewrite this as a NotificationState.
- */
- public static void checkFailedMessages() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- final Cursor messageDataCursor = db.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(),
- FailedMessageQuery.FAILED_MESSAGES_WHERE_CLAUSE,
- null /*selectionArgs*/,
- null /*groupBy*/,
- null /*having*/,
- FailedMessageQuery.FAILED_ORDER_BY);
-
- try {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(context);
- if (messageDataCursor != null) {
- final MessageData messageData = new MessageData();
-
- final HashSet<String> conversationsWithFailedMessages = new HashSet<String>();
-
- // track row ids in case we want to display something that requires this
- // information
- final ArrayList<Integer> failedMessages = new ArrayList<Integer>();
-
- int cursorPosition = -1;
- final long when = 0;
-
- messageDataCursor.moveToPosition(-1);
- while (messageDataCursor.moveToNext()) {
- messageData.bind(messageDataCursor);
-
- final String conversationId = messageData.getConversationId();
- if (DataModel.get().isNewMessageObservable(conversationId)) {
- // Don't post a system notification for an observable conversation
- // because we already show an angry red annotation in the conversation
- // itself or in the conversation preview snippet.
- continue;
- }
-
- cursorPosition = messageDataCursor.getPosition();
- failedMessages.add(cursorPosition);
- conversationsWithFailedMessages.add(conversationId);
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Found " + failedMessages.size() + " failed messages");
- }
- if (failedMessages.size() > 0) {
- final NotificationCompat.Builder builder =
- new NotificationCompat.Builder(context);
-
- CharSequence line1;
- CharSequence line2;
- final boolean isRichContent = false;
- ConversationIdSet conversationIds = null;
- PendingIntent destinationIntent;
- if (failedMessages.size() == 1) {
- messageDataCursor.moveToPosition(cursorPosition);
- messageData.bind(messageDataCursor);
- final String conversationId = messageData.getConversationId();
-
- // We have a single conversation, go directly to that conversation.
- destinationIntent = UIIntents.get()
- .getPendingIntentForConversationActivity(context,
- conversationId,
- null /*draft*/);
-
- conversationIds = ConversationIdSet.createSet(conversationId);
-
- final String failedMessgeSnippet = messageData.getMessageText();
- int failureStringId;
- if (messageData.getStatus() ==
- MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED) {
- failureStringId =
- R.string.notification_download_failures_line1_singular;
- } else {
- failureStringId = R.string.notification_send_failures_line1_singular;
- }
- line1 = resources.getString(failureStringId);
- line2 = failedMessgeSnippet;
- // Set rich text for non-SMS messages or MMS push notification messages
- // which we generate locally with rich text
- // TODO- fix this
-// if (messageData.isMmsInd()) {
-// isRichContent = true;
-// }
- } else {
- // We have notifications for multiple conversation, go to the conversation
- // list.
- destinationIntent = UIIntents.get()
- .getPendingIntentForConversationListActivity(context);
-
- int line1StringId;
- int line2PluralsId;
- if (messageData.getStatus() ==
- MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED) {
- line1StringId =
- R.string.notification_download_failures_line1_plural;
- line2PluralsId = R.plurals.notification_download_failures;
- } else {
- line1StringId = R.string.notification_send_failures_line1_plural;
- line2PluralsId = R.plurals.notification_send_failures;
- }
- line1 = resources.getString(line1StringId);
- line2 = resources.getQuantityString(
- line2PluralsId,
- conversationsWithFailedMessages.size(),
- failedMessages.size(),
- conversationsWithFailedMessages.size());
- }
- line1 = applyWarningTextColor(context, line1);
- line2 = applyWarningTextColor(context, line2);
-
- final PendingIntent pendingIntentForDelete =
- UIIntents.get().getPendingIntentForClearingNotifications(
- context,
- BugleNotifications.UPDATE_ERRORS,
- conversationIds,
- 0);
-
- builder
- .setContentTitle(line1)
- .setTicker(line1)
- .setWhen(when > 0 ? when : System.currentTimeMillis())
- .setSmallIcon(R.drawable.ic_failed_light)
- .setDeleteIntent(pendingIntentForDelete)
- .setContentIntent(destinationIntent)
- .setSound(UriUtil.getUriForResourceId(context, R.raw.message_failure));
- if (isRichContent && !TextUtils.isEmpty(line2)) {
- final NotificationCompat.InboxStyle inboxStyle =
- new NotificationCompat.InboxStyle(builder);
- if (line2 != null) {
- inboxStyle.addLine(Html.fromHtml(line2.toString()));
- }
- builder.setStyle(inboxStyle);
- } else {
- builder.setContentText(line2);
- }
-
- if (builder != null) {
- notificationManager.notify(
- BugleNotifications.buildNotificationTag(
- PendingIntentConstants.MSG_SEND_ERROR, null),
- PendingIntentConstants.MSG_SEND_ERROR,
- builder.build());
- }
- } else {
- notificationManager.cancel(
- BugleNotifications.buildNotificationTag(
- PendingIntentConstants.MSG_SEND_ERROR, null),
- PendingIntentConstants.MSG_SEND_ERROR);
- }
- }
- } finally {
- if (messageDataCursor != null) {
- messageDataCursor.close();
- }
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/MessageTextStats.java b/src/com/android/messaging/datamodel/MessageTextStats.java
deleted file mode 100644
index 2bd24ff..0000000
--- a/src/com/android/messaging/datamodel/MessageTextStats.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.telephony.SmsMessage;
-
-import com.android.messaging.sms.MmsConfig;
-
-public class MessageTextStats {
- private boolean mMessageLengthRequiresMms;
- private int mMessageCount;
- private int mCodePointsRemainingInCurrentMessage;
-
- public MessageTextStats() {
- mCodePointsRemainingInCurrentMessage = Integer.MAX_VALUE;
- }
-
- public int getNumMessagesToBeSent() {
- return mMessageCount;
- }
-
- public int getCodePointsRemainingInCurrentMessage() {
- return mCodePointsRemainingInCurrentMessage;
- }
-
- public boolean getMessageLengthRequiresMms() {
- return mMessageLengthRequiresMms;
- }
-
- public void updateMessageTextStats(final int selfSubId, final String messageText) {
- final int[] params = SmsMessage.calculateLength(messageText, false);
- /* SmsMessage.calculateLength returns an int[4] with:
- * int[0] being the number of SMS's required,
- * int[1] the number of code points used,
- * int[2] is the number of code points remaining until the next message.
- * int[3] is the encoding type that should be used for the message.
- */
- mMessageCount = params[0];
- mCodePointsRemainingInCurrentMessage = params[2];
-
- final MmsConfig mmsConfig = MmsConfig.get(selfSubId);
- if (!mmsConfig.getMultipartSmsEnabled() &&
- !mmsConfig.getSendMultipartSmsAsSeparateMessages()) {
- // The provider doesn't support multi-part sms's and we should use MMS to
- // send multi-part sms, so as soon as the user types
- // an sms longer than one segment, we have to turn the message into an mms.
- mMessageLengthRequiresMms = mMessageCount > 1;
- } else {
- final int threshold = mmsConfig.getSmsToMmsTextThreshold();
- mMessageLengthRequiresMms = threshold > 0 && mMessageCount > threshold;
- }
- // Some carriers require any SMS message longer than 80 to be sent as MMS
- // see b/12122333
- int smsToMmsLengthThreshold = mmsConfig.getSmsToMmsTextLengthThreshold();
- if (smsToMmsLengthThreshold > 0) {
- final int usedInCurrentMessage = params[1];
- /*
- * A little hacky way to find out if we should count characters in double bytes.
- * SmsMessage.calculateLength counts message code units based on the characters
- * in input. If all of them are ascii, the max length is
- * SmsMessage.MAX_USER_DATA_SEPTETS (160). If any of them are double-byte, like
- * Korean or Chinese, the max length is SmsMessage.MAX_USER_DATA_BYTES (140) bytes
- * (70 code units).
- * Here we check if the total code units we can use is smaller than 140. If so,
- * we know we should count threshold in double-byte, so divide the threshold by 2.
- * In this way, we will count Korean text correctly with regard to the length threshold.
- */
- if (usedInCurrentMessage + mCodePointsRemainingInCurrentMessage
- < SmsMessage.MAX_USER_DATA_BYTES) {
- smsToMmsLengthThreshold /= 2;
- }
- if (usedInCurrentMessage > smsToMmsLengthThreshold) {
- mMessageLengthRequiresMms = true;
- }
- }
- }
-
-}
diff --git a/src/com/android/messaging/datamodel/MessagingContentProvider.java b/src/com/android/messaging/datamodel/MessagingContentProvider.java
deleted file mode 100644
index 7688abd..0000000
--- a/src/com/android/messaging/datamodel/MessagingContentProvider.java
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel;
-
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
-
-import com.android.messaging.BugleApplication;
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.widget.BugleWidgetProvider;
-import com.android.messaging.widget.WidgetConversationProvider;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-
-/**
- * A centralized provider for Uris exposed by Bugle.
- * */
-public class MessagingContentProvider extends ContentProvider {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- @VisibleForTesting
- public static final String AUTHORITY =
- "com.android.messaging.datamodel.MessagingContentProvider";
- private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/';
-
- // Conversations query
- private static final String CONVERSATIONS_QUERY = "conversations";
-
- public static final Uri CONVERSATIONS_URI = Uri.parse(CONTENT_AUTHORITY + CONVERSATIONS_QUERY);
- static final Uri PARTS_URI = Uri.parse(CONTENT_AUTHORITY + DatabaseHelper.PARTS_TABLE);
-
- // Messages query
- private static final String MESSAGES_QUERY = "messages";
-
- static final Uri MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY + MESSAGES_QUERY);
-
- public static final Uri CONVERSATION_MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY +
- MESSAGES_QUERY + "/conversation");
-
- // Conversation participants query
- private static final String PARTICIPANTS_QUERY = "participants";
-
- static class ConversationParticipantsQueryColumns extends ParticipantColumns {
- static final String CONVERSATION_ID = ConversationParticipantsColumns.CONVERSATION_ID;
- }
-
- static final Uri CONVERSATION_PARTICIPANTS_URI = Uri.parse(CONTENT_AUTHORITY +
- PARTICIPANTS_QUERY + "/conversation");
-
- public static final Uri PARTICIPANTS_URI = Uri.parse(CONTENT_AUTHORITY + PARTICIPANTS_QUERY);
-
- // Conversation images query
- private static final String CONVERSATION_IMAGES_QUERY = "conversation_images";
-
- public static final Uri CONVERSATION_IMAGES_URI = Uri.parse(CONTENT_AUTHORITY +
- CONVERSATION_IMAGES_QUERY);
-
- private static final String DRAFT_IMAGES_QUERY = "draft_images";
-
- public static final Uri DRAFT_IMAGES_URI = Uri.parse(CONTENT_AUTHORITY +
- DRAFT_IMAGES_QUERY);
-
- /**
- * Notifies that <i>all</i> data exposed by the provider needs to be refreshed.
- * <p>
- * <b>IMPORTANT!</b> You probably shouldn't be calling this. Prefer to notify more specific
- * uri's instead. Currently only sync uses this, because sync can potentially update many
- * different tables at once.
- */
- public static void notifyEverythingChanged() {
- final Uri uri = Uri.parse(CONTENT_AUTHORITY);
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver cr = context.getContentResolver();
- cr.notifyChange(uri, null);
-
- // Notify any conversations widgets the conversation list has changed.
- BugleWidgetProvider.notifyConversationListChanged(context);
-
- // Notify all conversation widgets to update.
- WidgetConversationProvider.notifyMessagesChanged(context, null /*conversationId*/);
- }
-
- /**
- * Build a participant uri from the conversation id.
- */
- public static Uri buildConversationParticipantsUri(final String conversationId) {
- final Uri.Builder builder = CONVERSATION_PARTICIPANTS_URI.buildUpon();
- builder.appendPath(conversationId);
- return builder.build();
- }
-
- public static void notifyParticipantsChanged(final String conversationId) {
- final Uri uri = buildConversationParticipantsUri(conversationId);
- final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
- cr.notifyChange(uri, null);
- }
-
- public static void notifyAllMessagesChanged() {
- final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
- cr.notifyChange(CONVERSATION_MESSAGES_URI, null);
- }
-
- public static void notifyAllParticipantsChanged() {
- final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
- cr.notifyChange(CONVERSATION_PARTICIPANTS_URI, null);
- }
-
- // Default value for unknown dimension of image
- public static final int UNSPECIFIED_SIZE = -1;
-
- // Internal
- private static final int CONVERSATIONS_QUERY_CODE = 10;
-
- private static final int CONVERSATION_QUERY_CODE = 20;
- private static final int CONVERSATION_MESSAGES_QUERY_CODE = 30;
- private static final int CONVERSATION_PARTICIPANTS_QUERY_CODE = 40;
- private static final int CONVERSATION_IMAGES_QUERY_CODE = 50;
- private static final int DRAFT_IMAGES_QUERY_CODE = 60;
- private static final int PARTICIPANTS_QUERY_CODE = 70;
-
- // TODO: Move to a better structured URI namespace.
- private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- static {
- sURIMatcher.addURI(AUTHORITY, CONVERSATIONS_QUERY, CONVERSATIONS_QUERY_CODE);
- sURIMatcher.addURI(AUTHORITY, CONVERSATIONS_QUERY + "/*", CONVERSATION_QUERY_CODE);
- sURIMatcher.addURI(AUTHORITY, MESSAGES_QUERY + "/conversation/*",
- CONVERSATION_MESSAGES_QUERY_CODE);
- sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY + "/conversation/*",
- CONVERSATION_PARTICIPANTS_QUERY_CODE);
- sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY, PARTICIPANTS_QUERY_CODE);
- sURIMatcher.addURI(AUTHORITY, CONVERSATION_IMAGES_QUERY + "/*",
- CONVERSATION_IMAGES_QUERY_CODE);
- sURIMatcher.addURI(AUTHORITY, DRAFT_IMAGES_QUERY + "/*",
- DRAFT_IMAGES_QUERY_CODE);
- }
-
- /**
- * Build a messages uri from the conversation id.
- */
- public static Uri buildConversationMessagesUri(final String conversationId) {
- final Uri.Builder builder = CONVERSATION_MESSAGES_URI.buildUpon();
- builder.appendPath(conversationId);
- return builder.build();
- }
-
- public static void notifyMessagesChanged(final String conversationId) {
- final Uri uri = buildConversationMessagesUri(conversationId);
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver cr = context.getContentResolver();
- cr.notifyChange(uri, null);
- notifyConversationListChanged();
-
- // Notify the widget the messages changed
- WidgetConversationProvider.notifyMessagesChanged(context, conversationId);
- }
-
- /**
- * Build a conversation metadata uri from a conversation id.
- */
- public static Uri buildConversationMetadataUri(final String conversationId) {
- final Uri.Builder builder = CONVERSATIONS_URI.buildUpon();
- builder.appendPath(conversationId);
- return builder.build();
- }
-
- public static void notifyConversationMetadataChanged(final String conversationId) {
- final Uri uri = buildConversationMetadataUri(conversationId);
- final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
- cr.notifyChange(uri, null);
- notifyConversationListChanged();
- }
-
- public static void notifyPartsChanged() {
- final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
- cr.notifyChange(PARTS_URI, null);
- }
-
- public static void notifyConversationListChanged() {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver cr = context.getContentResolver();
- cr.notifyChange(CONVERSATIONS_URI, null);
-
- // Notify the widget the conversation list changed
- BugleWidgetProvider.notifyConversationListChanged(context);
- }
-
- /**
- * Build a conversation images uri from a conversation id.
- */
- public static Uri buildConversationImagesUri(final String conversationId) {
- final Uri.Builder builder = CONVERSATION_IMAGES_URI.buildUpon();
- builder.appendPath(conversationId);
- return builder.build();
- }
-
- /**
- * Build a draft images uri from a conversation id.
- */
- public static Uri buildDraftImagesUri(final String conversationId) {
- final Uri.Builder builder = DRAFT_IMAGES_URI.buildUpon();
- builder.appendPath(conversationId);
- return builder.build();
- }
-
- private DatabaseHelper mDatabaseHelper;
- private DatabaseWrapper mDatabaseWrapper;
-
- public MessagingContentProvider() {
- super();
- }
-
- @VisibleForTesting
- public void setDatabaseForTest(final DatabaseWrapper db) {
- Assert.isTrue(BugleApplication.isRunningTests());
- mDatabaseWrapper = db;
- }
-
- private DatabaseWrapper getDatabaseWrapper() {
- if (mDatabaseWrapper == null) {
- mDatabaseWrapper = mDatabaseHelper.getDatabase();
- }
- return mDatabaseWrapper;
- }
-
- @Override
- public Cursor query(final Uri uri, final String[] projection, String selection,
- final String[] selectionArgs, String sortOrder) {
-
- // Processes other than self are allowed to temporarily access the media
- // scratch space; we grant uri read access on a case-by-case basis. Dialer app and
- // contacts app would doQuery() on the vCard uri before trying to open the inputStream.
- // There's nothing that we need to return for this uri so just No-Op.
- //if (isMediaScratchSpaceUri(uri)) {
- // return null;
- //}
-
- final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
-
- String[] queryArgs = selectionArgs;
- final int match = sURIMatcher.match(uri);
- String groupBy = null;
- String limit = null;
- switch (match) {
- case CONVERSATIONS_QUERY_CODE:
- queryBuilder.setTables(ConversationListItemData.getConversationListView());
- // Hide empty conversations (ones with 0 sort_timestamp)
- queryBuilder.appendWhere(ConversationColumns.SORT_TIMESTAMP + " > 0 ");
- break;
- case CONVERSATION_QUERY_CODE:
- queryBuilder.setTables(ConversationListItemData.getConversationListView());
- if (uri.getPathSegments().size() == 2) {
- queryBuilder.appendWhere(ConversationColumns._ID + "=?");
- // Get the conversation id from the uri
- queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1));
- } else {
- throw new IllegalArgumentException("Malformed URI " + uri);
- }
- break;
- case CONVERSATION_PARTICIPANTS_QUERY_CODE:
- queryBuilder.setTables(DatabaseHelper.PARTICIPANTS_TABLE);
- if (uri.getPathSegments().size() == 3 &&
- TextUtils.equals(uri.getPathSegments().get(1), "conversation")) {
- queryBuilder.appendWhere(ParticipantColumns._ID + " IN ( " + "SELECT "
- + ConversationParticipantsColumns.PARTICIPANT_ID + " AS "
- + ParticipantColumns._ID
- + " FROM " + DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE
- + " WHERE " + ConversationParticipantsColumns.CONVERSATION_ID
- + " =? UNION SELECT " + ParticipantColumns._ID + " FROM "
- + DatabaseHelper.PARTICIPANTS_TABLE + " WHERE "
- + ParticipantColumns.SUB_ID + " != "
- + ParticipantData.OTHER_THAN_SELF_SUB_ID + " )");
- // Get the conversation id from the uri
- queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(2));
- } else {
- throw new IllegalArgumentException("Malformed URI " + uri);
- }
- break;
- case PARTICIPANTS_QUERY_CODE:
- queryBuilder.setTables(DatabaseHelper.PARTICIPANTS_TABLE);
- if (uri.getPathSegments().size() != 1) {
- throw new IllegalArgumentException("Malformed URI " + uri);
- }
- break;
- case CONVERSATION_MESSAGES_QUERY_CODE:
- if (uri.getPathSegments().size() == 3 &&
- TextUtils.equals(uri.getPathSegments().get(1), "conversation")) {
- // Get the conversation id from the uri
- final String conversationId = uri.getPathSegments().get(2);
-
- // We need to handle this query differently, instead of falling through to the
- // generic query call at the bottom. For performance reasons, the conversation
- // messages query is executed as a raw query. It is invalid to specify
- // selection/sorting for this query.
-
- if (selection == null && selectionArgs == null && sortOrder == null) {
- return queryConversationMessages(conversationId, uri);
- } else {
- throw new IllegalArgumentException(
- "Cannot set selection or sort order with this query");
- }
- } else {
- throw new IllegalArgumentException("Malformed URI " + uri);
- }
- case CONVERSATION_IMAGES_QUERY_CODE:
- queryBuilder.setTables(ConversationImagePartsView.getViewName());
- if (uri.getPathSegments().size() == 2) {
- // Exclude draft.
- queryBuilder.appendWhere(
- ConversationImagePartsView.Columns.CONVERSATION_ID + " =? AND " +
- ConversationImagePartsView.Columns.STATUS + "<>" +
- MessageData.BUGLE_STATUS_OUTGOING_DRAFT);
- // Get the conversation id from the uri
- queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1));
- } else {
- throw new IllegalArgumentException("Malformed URI " + uri);
- }
- break;
- case DRAFT_IMAGES_QUERY_CODE:
- queryBuilder.setTables(ConversationImagePartsView.getViewName());
- if (uri.getPathSegments().size() == 2) {
- // Draft only.
- queryBuilder.appendWhere(
- ConversationImagePartsView.Columns.CONVERSATION_ID + " =? AND " +
- ConversationImagePartsView.Columns.STATUS + "=" +
- MessageData.BUGLE_STATUS_OUTGOING_DRAFT);
- // Get the conversation id from the uri
- queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1));
- } else {
- throw new IllegalArgumentException("Malformed URI " + uri);
- }
- break;
- default: {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- }
-
- final Cursor cursor = getDatabaseWrapper().query(queryBuilder, projection, selection,
- queryArgs, groupBy, null, sortOrder, limit);
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
- return cursor;
- }
-
- private Cursor queryConversationMessages(final String conversationId, final Uri notifyUri) {
- final String[] queryArgs = { conversationId };
- final Cursor cursor = getDatabaseWrapper().rawQuery(
- ConversationMessageData.getConversationMessagesQuerySql(), queryArgs);
- cursor.setNotificationUri(getContext().getContentResolver(), notifyUri);
- return cursor;
- }
-
- @Override
- public String getType(final Uri uri) {
- final StringBuilder sb = new
- StringBuilder("vnd.android.cursor.dir/vnd.android.messaging.");
-
- switch (sURIMatcher.match(uri)) {
- case CONVERSATIONS_QUERY_CODE: {
- sb.append(CONVERSATIONS_QUERY);
- break;
- }
- default: {
- throw new IllegalArgumentException("Unknown URI: " + uri);
- }
- }
- return sb.toString();
- }
-
- protected DatabaseHelper getDatabase() {
- return DatabaseHelper.getInstance(getContext());
- }
-
- @Override
- public ParcelFileDescriptor openFile(final Uri uri, final String fileMode)
- throws FileNotFoundException {
- throw new IllegalArgumentException("openFile not supported: " + uri);
- }
-
- @Override
- public Uri insert(final Uri uri, final ContentValues values) {
- throw new IllegalStateException("Insert not supported " + uri);
- }
-
- @Override
- public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
- throw new IllegalArgumentException("Delete not supported: " + uri);
- }
-
- @Override
- public int update(final Uri uri, final ContentValues values, final String selection,
- final String[] selectionArgs) {
- throw new IllegalArgumentException("Update not supported: " + uri);
- }
-
- /**
- * Prepends new arguments to the existing argument list.
- *
- * @param oldArgList The current list of arguments. May be {@code null}
- * @param args The new arguments to prepend
- * @return A new argument list with the given arguments prepended
- */
- private String[] prependArgs(final String[] oldArgList, final String... args) {
- if (args == null || args.length == 0) {
- return oldArgList;
- }
- final int oldArgCount = (oldArgList == null ? 0 : oldArgList.length);
- final int newArgCount = args.length;
-
- final String[] newArgs = new String[oldArgCount + newArgCount];
- System.arraycopy(args, 0, newArgs, 0, newArgCount);
- if (oldArgCount > 0) {
- System.arraycopy(oldArgList, 0, newArgs, newArgCount, oldArgCount);
- }
- return newArgs;
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
- // First dump out the default SMS app package name
- String defaultSmsApp = PhoneUtils.getDefault().getDefaultSmsApp();
- if (TextUtils.isEmpty(defaultSmsApp)) {
- if (OsUtil.isAtLeastKLP()) {
- defaultSmsApp = "None";
- } else {
- defaultSmsApp = "None (pre-Kitkat)";
- }
- }
- writer.println("Default SMS app: " + defaultSmsApp);
- // Now dump logs
- LogUtil.dump(writer);
- }
-
- @Override
- public boolean onCreate() {
- // This is going to wind up calling into createDatabase() below.
- mDatabaseHelper = (DatabaseHelper) getDatabase();
- // We cannot initialize mDatabaseWrapper yet as the Factory may not be initialized
- return true;
- }
-}
diff --git a/src/com/android/messaging/datamodel/MmsFileProvider.java b/src/com/android/messaging/datamodel/MmsFileProvider.java
deleted file mode 100644
index 0022630..0000000
--- a/src/com/android/messaging/datamodel/MmsFileProvider.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.net.Uri;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.LogUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.File;
-
-/**
- * A very simple content provider that can serve mms files from our cache directory.
- */
-public class MmsFileProvider extends FileProvider {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- @VisibleForTesting
- static final String AUTHORITY = "com.android.messaging.datamodel.MmsFileProvider";
- private static final String RAW_MMS_DIR = "rawmms";
-
- /**
- * Returns a uri that can be used to access a raw mms file.
- *
- * @return the URI for an raw mms file
- */
- public static Uri buildRawMmsUri() {
- final Uri uri = FileProvider.buildFileUri(AUTHORITY, null);
- final File file = getFile(uri.getPath());
- if (!ensureFileExists(file)) {
- LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath());
- }
- return uri;
- }
-
- @Override
- File getFile(final String path, final String extension) {
- return getFile(path);
- }
-
- public static File getFile(final Uri uri) {
- return getFile(uri.getPath());
- }
-
- private static File getFile(final String path) {
- final Context context = Factory.get().getApplicationContext();
- return new File(getDirectory(context), path + ".dat");
- }
-
- private static File getDirectory(final Context context) {
- return new File(context.getCacheDir(), RAW_MMS_DIR);
- }
-}
diff --git a/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java b/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java
deleted file mode 100644
index 791ff34..0000000
--- a/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.RemoteInput;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.action.InsertNewMessageAction;
-import com.android.messaging.datamodel.action.UpdateMessageNotificationAction;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.conversationlist.ConversationListActivity;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Respond to a special intent and send an SMS message without the user's intervention, unless
- * the intent extra "showUI" is true.
- */
-public class NoConfirmationSmsSendService extends IntentService {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final String EXTRA_SUBSCRIPTION = "subscription";
- public static final String EXTRA_SELF_ID = "self_id";
-
- public NoConfirmationSmsSendService() {
- // Class name will be the thread name.
- super(NoConfirmationSmsSendService.class.getName());
-
- // Intent should be redelivered if the process gets killed before completing the job.
- setIntentRedelivery(true);
- }
-
- @Override
- protected void onHandleIntent(final Intent intent) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "NoConfirmationSmsSendService onHandleIntent");
- }
-
- final String action = intent.getAction();
- if (!TelephonyManager.ACTION_RESPOND_VIA_MESSAGE.equals(action)) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "NoConfirmationSmsSendService onHandleIntent wrong action: " +
- action);
- }
- return;
- }
- final Bundle extras = intent.getExtras();
- if (extras == null) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Called to send SMS but no extras");
- }
- return;
- }
-
- // Get all possible extras from intent
- final String conversationId =
- intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- final String selfId = intent.getStringExtra(EXTRA_SELF_ID);
- final boolean requiresMms = intent.getBooleanExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS,
- false);
- final String message = getText(intent, Intent.EXTRA_TEXT);
- final String subject = getText(intent, Intent.EXTRA_SUBJECT);
- final int subId = extras.getInt(EXTRA_SUBSCRIPTION, ParticipantData.DEFAULT_SELF_SUB_ID);
-
- final Uri intentUri = intent.getData();
- final String recipients = intentUri != null ? MmsUtils.getSmsRecipients(intentUri) : null;
-
- if (TextUtils.isEmpty(recipients) && TextUtils.isEmpty(conversationId)) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Both conversationId and recipient(s) cannot be empty");
- }
- return;
- }
-
- if (extras.getBoolean("showUI", false)) {
- startActivity(new Intent(this, ConversationListActivity.class));
- } else {
- if (TextUtils.isEmpty(message)) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Message cannot be empty");
- }
- return;
- }
-
- // TODO: it's possible that a long message would require sending it via mms,
- // but we're not testing for that here and we're sending the message as an sms.
-
- if (TextUtils.isEmpty(conversationId)) {
- InsertNewMessageAction.insertNewMessage(subId, recipients, message, subject);
- } else {
- MessageData messageData = null;
- if (requiresMms) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Auto-sending MMS message in conversation: " +
- conversationId);
- }
- messageData = MessageData.createDraftMmsMessage(conversationId, selfId, message,
- subject);
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Auto-sending SMS message in conversation: " +
- conversationId);
- }
- messageData = MessageData.createDraftSmsMessage(conversationId, selfId,
- message);
- }
- InsertNewMessageAction.insertNewMessage(messageData);
- }
- UpdateMessageNotificationAction.updateMessageNotification();
- }
- }
-
- private String getText(final Intent intent, final String textType) {
- final String message = intent.getStringExtra(textType);
- if (message == null) {
- final Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
- if (remoteInput != null) {
- final CharSequence extra = remoteInput.getCharSequence(textType);
- if (extra != null) {
- return extra.toString();
- }
- }
- }
- return message;
- }
-
-}
diff --git a/src/com/android/messaging/datamodel/NotificationState.java b/src/com/android/messaging/datamodel/NotificationState.java
deleted file mode 100644
index d589874..0000000
--- a/src/com/android/messaging/datamodel/NotificationState.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel;
-
-import android.app.PendingIntent;
-import android.net.Uri;
-import android.support.v4.app.NotificationCompat;
-
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.ConversationIdSet;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-
-/**
- * Base class for representing notifications. The main reason for this class is that in order to
- * show pictures or avatars they might need to be loaded in the background. This class and
- * subclasses can do the main work to get the notification ready and then wait until any images
- * that are needed are ready before posting.
- *
- * The creation of a notification is split into two parts. The NotificationState ctor should
- * setup the basic information including the mContentIntent. A Notification Builder is created in
- * RealTimeChatNotifications and passed to the build() method of each notification where the
- * Notification is fully specified.
- *
- * TODO: There is still some duplication and inconsistency in the utility functions and
- * placement of different building blocks across notification types (e.g. summary text for accounts)
- */
-public abstract class NotificationState {
- private static final int CONTENT_INTENT_REQUEST_CODE_OFFSET = 0;
- private static final int CLEAR_INTENT_REQUEST_CODE_OFFSET = 1;
- private static final int NUM_REQUEST_CODES_NEEDED = 2;
-
- public interface FailedMessageQuery {
- static final String FAILED_MESSAGES_WHERE_CLAUSE =
- "((" + MessageColumns.STATUS + " = " +
- MessageData.BUGLE_STATUS_OUTGOING_FAILED + " OR " +
- MessageColumns.STATUS + " = " +
- MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED + ") AND " +
- DatabaseHelper.MessageColumns.SEEN + " = 0)";
-
- static final String FAILED_ORDER_BY = DatabaseHelper.MessageColumns.CONVERSATION_ID + ", " +
- DatabaseHelper.MessageColumns.SENT_TIMESTAMP + " asc";
- }
-
- public final ConversationIdSet mConversationIds;
- public final HashSet<String> mPeople;
-
- public NotificationCompat.Style mNotificationStyle;
- public NotificationCompat.Builder mNotificationBuilder;
- public boolean mCanceled;
- public int mType;
- public int mBaseRequestCode;
- public ArrayList<Uri> mParticipantAvatarsUris = null;
- public ArrayList<Uri> mParticipantContactUris = null;
-
- NotificationState(final ConversationIdSet conversationIds) {
- mConversationIds = conversationIds;
- mPeople = new HashSet<String>();
- }
-
- /**
- * The intent to be triggered when the notification is dismissed.
- */
- public abstract PendingIntent getClearIntent();
-
- protected Uri getAttachmentUri() {
- return null;
- }
-
- // Returns the mime type of the attachment (See ContentType class for definitions)
- protected String getAttachmentType() {
- return null;
- }
-
- /**
- * Build the notification using the given builder.
- * @param builder
- * @return The style of the notification.
- */
- protected abstract NotificationCompat.Style build(NotificationCompat.Builder builder);
-
- protected void setAvatarUrlsForConversation(final String conversationId) {
- }
-
- protected void setPeopleForConversation(final String conversationId) {
- }
-
- /**
- * Reserves request codes for this notification type. By default 2 codes are reserved, one for
- * the main intent and another for the cancel intent. Override this function to reserve more.
- */
- public int getNumRequestCodesNeeded() {
- return NUM_REQUEST_CODES_NEEDED;
- }
-
- public int getContentIntentRequestCode() {
- return mBaseRequestCode + CONTENT_INTENT_REQUEST_CODE_OFFSET;
- }
-
- public int getClearIntentRequestCode() {
- return mBaseRequestCode + CLEAR_INTENT_REQUEST_CODE_OFFSET;
- }
-
- /**
- * Gets the appropriate icon needed for notifications.
- */
- public abstract int getIcon();
-
- /**
- * @return the type of notification that should be used from {@link RealTimeChatNotifications}
- * so that the proper ringtone and vibrate settings can be used.
- */
- public int getLatestMessageNotificationType() {
- return BugleNotifications.LOCAL_SMS_NOTIFICATION;
- }
-
- /**
- * @return the notification priority level for this notification.
- */
- public abstract int getPriority();
-
- /** @return custom ringtone URI or null if not set */
- public String getRingtoneUri() {
- return null;
- }
-
- public boolean getNotificationVibrate() {
- return false;
- }
-
- public long getLatestReceivedTimestamp() {
- return Long.MIN_VALUE;
- }
-}
diff --git a/src/com/android/messaging/datamodel/ParticipantRefresh.java b/src/com/android/messaging/datamodel/ParticipantRefresh.java
deleted file mode 100644
index 5324496..0000000
--- a/src/com/android/messaging/datamodel/ParticipantRefresh.java
+++ /dev/null
@@ -1,738 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.ContentValues;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.graphics.Color;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.support.v4.util.ArrayMap;
-import android.telephony.SubscriptionInfo;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Utility class for refreshing participant information based on matching contact. This updates
- * 1. name, photo_uri, matching contact_id of participants.
- * 2. generated_name of conversations.
- *
- * There are two kinds of participant refreshes,
- * 1. Full refresh, this is triggered at application start or activity resumes after contact
- * change is detected.
- * 2. Partial refresh, this is triggered when a participant is added to a conversation. This
- * normally happens during SMS sync.
- */
-@VisibleForTesting
-public class ParticipantRefresh {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- /**
- * Refresh all participants including ones that were resolved before.
- */
- public static final int REFRESH_MODE_FULL = 0;
-
- /**
- * Refresh all unresolved participants.
- */
- public static final int REFRESH_MODE_INCREMENTAL = 1;
-
- /**
- * Force refresh all self participants.
- */
- public static final int REFRESH_MODE_SELF_ONLY = 2;
-
- public static class ConversationParticipantsQuery {
- public static final String[] PROJECTION = new String[] {
- ConversationParticipantsColumns._ID,
- ConversationParticipantsColumns.CONVERSATION_ID,
- ConversationParticipantsColumns.PARTICIPANT_ID
- };
-
- public static final int INDEX_ID = 0;
- public static final int INDEX_CONVERSATION_ID = 1;
- public static final int INDEX_PARTICIPANT_ID = 2;
- }
-
- // Track whether observer is initialized or not.
- private static volatile boolean sObserverInitialized = false;
- private static final Object sLock = new Object();
- private static final AtomicBoolean sFullRefreshScheduled = new AtomicBoolean(false);
- private static final Runnable sFullRefreshRunnable = new Runnable() {
- @Override
- public void run() {
- final boolean oldScheduled = sFullRefreshScheduled.getAndSet(false);
- Assert.isTrue(oldScheduled);
- refreshParticipants(REFRESH_MODE_FULL);
- }
- };
- private static final Runnable sSelfOnlyRefreshRunnable = new Runnable() {
- @Override
- public void run() {
- refreshParticipants(REFRESH_MODE_SELF_ONLY);
- }
- };
-
- /**
- * A customized content resolver to track contact changes.
- */
- public static class ContactContentObserver extends ContentObserver {
- private volatile boolean mContactChanged = false;
-
- public ContactContentObserver() {
- super(null);
- }
-
- @Override
- public void onChange(final boolean selfChange) {
- super.onChange(selfChange);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Contacts changed");
- }
- mContactChanged = true;
- }
-
- public boolean getContactChanged() {
- return mContactChanged;
- }
-
- public void resetContactChanged() {
- mContactChanged = false;
- }
-
- public void initialize() {
- // TODO: Handle enterprise contacts post M once contacts provider supports it
- Factory.get().getApplicationContext().getContentResolver().registerContentObserver(
- Phone.CONTENT_URI, true, this);
- mContactChanged = true; // Force a full refresh on initialization.
- }
- }
-
- /**
- * Refresh participants only if needed, i.e., application start or contact changed.
- */
- public static void refreshParticipantsIfNeeded() {
- if (ParticipantRefresh.getNeedFullRefresh() &&
- sFullRefreshScheduled.compareAndSet(false, true)) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Started full participant refresh");
- }
- SafeAsyncTask.executeOnThreadPool(sFullRefreshRunnable);
- } else if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Skipped full participant refresh");
- }
- }
-
- /**
- * Refresh self participants on subscription or settings change.
- */
- public static void refreshSelfParticipants() {
- SafeAsyncTask.executeOnThreadPool(sSelfOnlyRefreshRunnable);
- }
-
- private static boolean getNeedFullRefresh() {
- final ContactContentObserver observer = Factory.get().getContactContentObserver();
- if (observer == null) {
- // If there is no observer (for unittest cases), we don't need to refresh participants.
- return false;
- }
-
- if (!sObserverInitialized) {
- synchronized (sLock) {
- if (!sObserverInitialized) {
- observer.initialize();
- sObserverInitialized = true;
- }
- }
- }
-
- return observer.getContactChanged();
- }
-
- private static void resetNeedFullRefresh() {
- final ContactContentObserver observer = Factory.get().getContactContentObserver();
- if (observer != null) {
- observer.resetContactChanged();
- }
- }
-
- /**
- * This class is totally static. Make constructor to be private so that an instance
- * of this class would not be created by by mistake.
- */
- private ParticipantRefresh() {
- }
-
- /**
- * Refresh participants in Bugle.
- *
- * @param refreshMode the refresh mode desired. See {@link #REFRESH_MODE_FULL},
- * {@link #REFRESH_MODE_INCREMENTAL}, and {@link #REFRESH_MODE_SELF_ONLY}
- */
- @VisibleForTesting
- static void refreshParticipants(final int refreshMode) {
- Assert.inRange(refreshMode, REFRESH_MODE_FULL, REFRESH_MODE_SELF_ONLY);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- switch (refreshMode) {
- case REFRESH_MODE_FULL:
- LogUtil.v(TAG, "Start full participant refresh");
- break;
- case REFRESH_MODE_INCREMENTAL:
- LogUtil.v(TAG, "Start partial participant refresh");
- break;
- case REFRESH_MODE_SELF_ONLY:
- LogUtil.v(TAG, "Start self participant refresh");
- break;
- }
- }
-
- if (!ContactUtil.hasReadContactsPermission() || !OsUtil.hasPhonePermission()) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Skipping participant referesh because of permissions");
- }
- return;
- }
-
- if (refreshMode == REFRESH_MODE_FULL) {
- // resetNeedFullRefresh right away so that we will skip duplicated full refresh
- // requests.
- resetNeedFullRefresh();
- }
-
- if (refreshMode == REFRESH_MODE_FULL || refreshMode == REFRESH_MODE_SELF_ONLY) {
- refreshSelfParticipantList();
- }
-
- final ArrayList<String> changedParticipants = new ArrayList<String>();
-
- String selection = null;
- String[] selectionArgs = null;
-
- if (refreshMode == REFRESH_MODE_INCREMENTAL) {
- // In case of incremental refresh, filter out participants that are already resolved.
- selection = ParticipantColumns.CONTACT_ID + "=?";
- selectionArgs = new String[] {
- String.valueOf(ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED) };
- } else if (refreshMode == REFRESH_MODE_SELF_ONLY) {
- // In case of self-only refresh, filter out non-self participants.
- selection = SELF_PARTICIPANTS_CLAUSE;
- selectionArgs = null;
- }
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
- Cursor cursor = null;
- boolean selfUpdated = false;
- try {
- cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
- ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null);
-
- if (cursor != null) {
- while (cursor.moveToNext()) {
- try {
- final ParticipantData participantData =
- ParticipantData.getFromCursor(cursor);
- if (refreshParticipant(db, participantData)) {
- if (participantData.isSelf()) {
- selfUpdated = true;
- }
- updateParticipant(db, participantData);
- final String id = participantData.getId();
- changedParticipants.add(id);
- }
- } catch (final Exception exception) {
- // Failure to update one participant shouldn't cancel the entire refresh.
- // Log the failure so we know what's going on and resume the loop.
- LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "ParticipantRefresh: Failed to " +
- "update participant", exception);
- }
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Number of participants refreshed:" + changedParticipants.size());
- }
-
- // Refresh conversations for participants that are changed.
- if (changedParticipants.size() > 0) {
- BugleDatabaseOperations.refreshConversationsForParticipants(changedParticipants);
- }
- if (selfUpdated) {
- // Boom
- MessagingContentProvider.notifyAllParticipantsChanged();
- MessagingContentProvider.notifyAllMessagesChanged();
- }
- }
-
- private static final String SELF_PARTICIPANTS_CLAUSE = ParticipantColumns.SUB_ID
- + " NOT IN ( "
- + ParticipantData.OTHER_THAN_SELF_SUB_ID
- + " )";
-
- private static final Set<Integer> getExistingSubIds() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final HashSet<Integer> existingSubIds = new HashSet<Integer>();
-
- Cursor cursor = null;
- try {
- cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
- ParticipantsQuery.PROJECTION,
- SELF_PARTICIPANTS_CLAUSE, null, null, null, null);
-
- if (cursor != null) {
- while (cursor.moveToNext()) {
- final int subId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID);
- existingSubIds.add(subId);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return existingSubIds;
- }
-
- private static final String UPDATE_SELF_PARTICIPANT_SUBSCRIPTION_SQL =
- "UPDATE " + DatabaseHelper.PARTICIPANTS_TABLE + " SET "
- + ParticipantColumns.SIM_SLOT_ID + " = %d, "
- + ParticipantColumns.SUBSCRIPTION_COLOR + " = %d, "
- + ParticipantColumns.SUBSCRIPTION_NAME + " = %s "
- + " WHERE %s";
-
- static String getUpdateSelfParticipantSubscriptionInfoSql(final int slotId,
- final int subscriptionColor, final String subscriptionName, final String where) {
- return String.format((Locale) null /* construct SQL string without localization */,
- UPDATE_SELF_PARTICIPANT_SUBSCRIPTION_SQL,
- slotId, subscriptionColor, subscriptionName, where);
- }
-
- /**
- * Ensure that there is a self participant corresponding to every active SIM. Also, ensure
- * that any other older SIM self participants are marked as inactive.
- */
- private static void refreshSelfParticipantList() {
- if (!OsUtil.isAtLeastL_MR1()) {
- return;
- }
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- final List<SubscriptionInfo> subInfoRecords =
- PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList();
- final ArrayMap<Integer, SubscriptionInfo> activeSubscriptionIdToRecordMap =
- new ArrayMap<Integer, SubscriptionInfo>();
- db.beginTransaction();
- final Set<Integer> existingSubIds = getExistingSubIds();
-
- try {
- if (subInfoRecords != null) {
- for (final SubscriptionInfo subInfoRecord : subInfoRecords) {
- final int subId = subInfoRecord.getSubscriptionId();
- // If its a new subscription, add it to the database.
- if (!existingSubIds.contains(subId)) {
- db.execSQL(DatabaseHelper.getCreateSelfParticipantSql(subId));
- // Add it to the local set to guard against duplicated entries returned
- // by subscription manager.
- existingSubIds.add(subId);
- }
- activeSubscriptionIdToRecordMap.put(subId, subInfoRecord);
-
- if (subId == PhoneUtils.getDefault().getDefaultSmsSubscriptionId()) {
- // This is the system default subscription, so update the default self.
- activeSubscriptionIdToRecordMap.put(ParticipantData.DEFAULT_SELF_SUB_ID,
- subInfoRecord);
- }
- }
- }
-
- // For subscriptions already in the database, refresh ParticipantColumns.SIM_SLOT_ID.
- for (final Integer subId : activeSubscriptionIdToRecordMap.keySet()) {
- final SubscriptionInfo record = activeSubscriptionIdToRecordMap.get(subId);
- final String displayName =
- DatabaseUtils.sqlEscapeString(record.getDisplayName().toString());
- db.execSQL(getUpdateSelfParticipantSubscriptionInfoSql(record.getSimSlotIndex(),
- record.getIconTint(), displayName,
- ParticipantColumns.SUB_ID + " = " + subId));
- }
- db.execSQL(getUpdateSelfParticipantSubscriptionInfoSql(
- ParticipantData.INVALID_SLOT_ID, Color.TRANSPARENT, "''",
- ParticipantColumns.SUB_ID + " NOT IN (" +
- Joiner.on(", ").join(activeSubscriptionIdToRecordMap.keySet()) + ")"));
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- // Fix up conversation self ids by reverting to default self for conversations whose self
- // ids are no longer active.
- refreshConversationSelfIds();
- }
-
- /**
- * Refresh one participant.
- * @return true if the ParticipantData was changed
- */
- public static boolean refreshParticipant(final DatabaseWrapper db,
- final ParticipantData participantData) {
- boolean updated = false;
-
- if (participantData.isSelf()) {
- final int selfChange = refreshFromSelfProfile(db, participantData);
-
- if (selfChange == SELF_PROFILE_EXISTS) {
- // If a self-profile exists, it takes precedence over Contacts data. So we are done.
- return true;
- }
-
- updated = (selfChange == SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED);
-
- // Fall-through and try to update based on Contacts data
- }
-
- updated |= refreshFromContacts(db, participantData);
- return updated;
- }
-
- private static final int SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED = 1;
- private static final int SELF_PROFILE_EXISTS = 2;
-
- private static int refreshFromSelfProfile(final DatabaseWrapper db,
- final ParticipantData participantData) {
- int changed = 0;
- // Refresh the phone number based on information from telephony
- if (participantData.updatePhoneNumberForSelfIfChanged()) {
- changed = SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED;
- }
-
- if (OsUtil.isAtLeastL_MR1()) {
- // Refresh the subscription info based on information from SubscriptionManager.
- final SubscriptionInfo subscriptionInfo =
- PhoneUtils.get(participantData.getSubId()).toLMr1().getActiveSubscriptionInfo();
- if (participantData.updateSubscriptionInfoForSelfIfChanged(subscriptionInfo)) {
- changed = SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED;
- }
- }
-
- // For self participant, try getting name/avatar from self profile in CP2 first.
- // TODO: in case of multi-sim, profile would not be able to be used for
- // different numbers. Need to figure out that.
- Cursor selfCursor = null;
- try {
- selfCursor = ContactUtil.getSelf(db.getContext()).performSynchronousQuery();
- if (selfCursor != null && selfCursor.getCount() > 0) {
- selfCursor.moveToNext();
- final long selfContactId = selfCursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- participantData.setContactId(selfContactId);
- participantData.setFullName(selfCursor.getString(
- ContactUtil.INDEX_DISPLAY_NAME));
- participantData.setFirstName(
- ContactUtil.lookupFirstName(db.getContext(), selfContactId));
- participantData.setProfilePhotoUri(selfCursor.getString(
- ContactUtil.INDEX_PHOTO_URI));
- participantData.setLookupKey(selfCursor.getString(
- ContactUtil.INDEX_SELF_QUERY_LOOKUP_KEY));
- return SELF_PROFILE_EXISTS;
- }
- } catch (final Exception exception) {
- // It's possible for contact query to fail and we don't want that to crash our app.
- // However, we need to at least log the exception so we know something was wrong.
- LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Participant refresh: failed to refresh " +
- "participant. exception=" + exception);
- } finally {
- if (selfCursor != null) {
- selfCursor.close();
- }
- }
- return changed;
- }
-
- private static boolean refreshFromContacts(final DatabaseWrapper db,
- final ParticipantData participantData) {
- final String normalizedDestination = participantData.getNormalizedDestination();
- final long currentContactId = participantData.getContactId();
- final String currentDisplayName = participantData.getFullName();
- final String currentFirstName = participantData.getFirstName();
- final String currentPhotoUri = participantData.getProfilePhotoUri();
- final String currentContactDestination = participantData.getContactDestination();
-
- Cursor matchingContactCursor = null;
- long matchingContactId = -1;
- String matchingDisplayName = null;
- String matchingFirstName = null;
- String matchingPhotoUri = null;
- String matchingLookupKey = null;
- String matchingDestination = null;
- boolean updated = false;
-
- if (TextUtils.isEmpty(normalizedDestination)) {
- // The normalized destination can be "" for the self id if we can't get it from the
- // SIM. Some contact providers throw an IllegalArgumentException if you lookup "",
- // so we early out.
- return false;
- }
-
- try {
- matchingContactCursor = ContactUtil.lookupDestination(db.getContext(),
- normalizedDestination).performSynchronousQuery();
- if (matchingContactCursor == null || matchingContactCursor.getCount() == 0) {
- // If there is no match, mark the participant as contact not found.
- if (currentContactId != ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND) {
- participantData.setContactId(ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND);
- participantData.setFullName(null);
- participantData.setFirstName(null);
- participantData.setProfilePhotoUri(null);
- participantData.setLookupKey(null);
- updated = true;
- }
- return updated;
- }
-
- while (matchingContactCursor.moveToNext()) {
- final long contactId = matchingContactCursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- // Pick either the first contact or the contact with same id as previous matched
- // contact id.
- if (matchingContactId == -1 || currentContactId == contactId) {
- matchingContactId = contactId;
- matchingDisplayName = matchingContactCursor.getString(
- ContactUtil.INDEX_DISPLAY_NAME);
- matchingFirstName = ContactUtil.lookupFirstName(db.getContext(), contactId);
- matchingPhotoUri = matchingContactCursor.getString(
- ContactUtil.INDEX_PHOTO_URI);
- matchingLookupKey = matchingContactCursor.getString(
- ContactUtil.INDEX_LOOKUP_KEY);
- matchingDestination = matchingContactCursor.getString(
- ContactUtil.INDEX_PHONE_EMAIL);
- }
-
- // There is no need to try other contacts if the current contactId was not filled...
- if (currentContactId < 0
- // or we found the matching contact id
- || currentContactId == contactId) {
- break;
- }
- }
- } catch (final Exception exception) {
- // It's possible for contact query to fail and we don't want that to crash our app.
- // However, we need to at least log the exception so we know something was wrong.
- LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Participant refresh: failed to refresh " +
- "participant. exception=" + exception);
- return false;
- } finally {
- if (matchingContactCursor != null) {
- matchingContactCursor.close();
- }
- }
-
- // Update participant only if something changed.
- final boolean isContactIdChanged = (matchingContactId != currentContactId);
- final boolean isDisplayNameChanged =
- !TextUtils.equals(matchingDisplayName, currentDisplayName);
- final boolean isFirstNameChanged = !TextUtils.equals(matchingFirstName, currentFirstName);
- final boolean isPhotoUrlChanged = !TextUtils.equals(matchingPhotoUri, currentPhotoUri);
- final boolean isDestinationChanged = !TextUtils.equals(matchingDestination,
- currentContactDestination);
-
- if (isContactIdChanged || isDisplayNameChanged || isFirstNameChanged || isPhotoUrlChanged
- || isDestinationChanged) {
- participantData.setContactId(matchingContactId);
- participantData.setFullName(matchingDisplayName);
- participantData.setFirstName(matchingFirstName);
- participantData.setProfilePhotoUri(matchingPhotoUri);
- participantData.setLookupKey(matchingLookupKey);
- participantData.setContactDestination(matchingDestination);
- if (isDestinationChanged) {
- // Update the send destination to the new one entered by user in Contacts.
- participantData.setSendDestination(matchingDestination);
- }
- updated = true;
- }
-
- return updated;
- }
-
- /**
- * Update participant with matching contact's contactId, displayName and photoUri.
- */
- private static void updateParticipant(final DatabaseWrapper db,
- final ParticipantData participantData) {
- final ContentValues values = new ContentValues();
- if (participantData.isSelf()) {
- // Self participants can refresh their normalized phone numbers
- values.put(ParticipantColumns.NORMALIZED_DESTINATION,
- participantData.getNormalizedDestination());
- values.put(ParticipantColumns.DISPLAY_DESTINATION,
- participantData.getDisplayDestination());
- }
- values.put(ParticipantColumns.CONTACT_ID, participantData.getContactId());
- values.put(ParticipantColumns.LOOKUP_KEY, participantData.getLookupKey());
- values.put(ParticipantColumns.FULL_NAME, participantData.getFullName());
- values.put(ParticipantColumns.FIRST_NAME, participantData.getFirstName());
- values.put(ParticipantColumns.PROFILE_PHOTO_URI, participantData.getProfilePhotoUri());
- values.put(ParticipantColumns.CONTACT_DESTINATION, participantData.getContactDestination());
- values.put(ParticipantColumns.SEND_DESTINATION, participantData.getSendDestination());
-
- db.beginTransaction();
- try {
- db.update(DatabaseHelper.PARTICIPANTS_TABLE, values, ParticipantColumns._ID + "=?",
- new String[] { participantData.getId() });
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- /**
- * Get a list of inactive self ids in the participants table.
- */
- private static List<String> getInactiveSelfParticipantIds() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final List<String> inactiveSelf = new ArrayList<String>();
-
- final String selection = ParticipantColumns.SIM_SLOT_ID + "=? AND " +
- SELF_PARTICIPANTS_CLAUSE;
- Cursor cursor = null;
- try {
- cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
- new String[] { ParticipantColumns._ID },
- selection, new String[] { String.valueOf(ParticipantData.INVALID_SLOT_ID) },
- null, null, null);
-
- if (cursor != null) {
- while (cursor.moveToNext()) {
- final String participantId = cursor.getString(0);
- inactiveSelf.add(participantId);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return inactiveSelf;
- }
-
- /**
- * Gets a list of conversations with the given self ids.
- */
- private static List<String> getConversationsWithSelfParticipantIds(final List<String> selfIds) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final List<String> conversationIds = new ArrayList<String>();
-
- Cursor cursor = null;
- try {
- final StringBuilder selectionList = new StringBuilder();
- for (int i = 0; i < selfIds.size(); i++) {
- selectionList.append('?');
- if (i < selfIds.size() - 1) {
- selectionList.append(',');
- }
- }
- final String selection =
- ConversationColumns.CURRENT_SELF_ID + " IN (" + selectionList + ")";
- cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
- new String[] { ConversationColumns._ID },
- selection, selfIds.toArray(new String[0]),
- null, null, null);
-
- if (cursor != null) {
- while (cursor.moveToNext()) {
- final String conversationId = cursor.getString(0);
- conversationIds.add(conversationId);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return conversationIds;
- }
-
- /**
- * Refresh one conversation's self id.
- */
- private static void updateConversationSelfId(final String conversationId,
- final String selfId) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- db.beginTransaction();
- try {
- BugleDatabaseOperations.updateConversationSelfIdInTransaction(db, conversationId,
- selfId);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
- UIIntents.get().broadcastConversationSelfIdChange(db.getContext(), conversationId, selfId);
- }
-
- /**
- * After refreshing the self participant list, find all conversations with inactive self ids,
- * and switch them back to system default.
- */
- private static void refreshConversationSelfIds() {
- final List<String> inactiveSelfs = getInactiveSelfParticipantIds();
- if (inactiveSelfs.size() == 0) {
- return;
- }
- final List<String> conversationsToRefresh =
- getConversationsWithSelfParticipantIds(inactiveSelfs);
- if (conversationsToRefresh.size() == 0) {
- return;
- }
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final ParticipantData defaultSelf =
- BugleDatabaseOperations.getOrCreateSelf(db, ParticipantData.DEFAULT_SELF_SUB_ID);
-
- if (defaultSelf != null) {
- for (final String conversationId : conversationsToRefresh) {
- updateConversationSelfId(conversationId, defaultSelf.getId());
- }
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/SyncManager.java b/src/com/android/messaging/datamodel/SyncManager.java
deleted file mode 100644
index b3571bf..0000000
--- a/src/com/android/messaging/datamodel/SyncManager.java
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.provider.Telephony;
-import android.support.v4.util.LongSparseArray;
-
-import com.android.messaging.datamodel.action.SyncMessagesAction;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.BuglePrefsKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.collect.Lists;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * This class manages message sync with the Telephony SmsProvider/MmsProvider.
- */
-public class SyncManager {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- /**
- * Record of any user customization to conversation settings
- */
- public static class ConversationCustomization {
- private final boolean mArchived;
- private final boolean mMuted;
- private final boolean mNoVibrate;
- private final String mNotificationSoundUri;
-
- public ConversationCustomization(final boolean archived, final boolean muted,
- final boolean noVibrate, final String notificationSoundUri) {
- mArchived = archived;
- mMuted = muted;
- mNoVibrate = noVibrate;
- mNotificationSoundUri = notificationSoundUri;
- }
-
- public boolean isArchived() {
- return mArchived;
- }
-
- public boolean isMuted() {
- return mMuted;
- }
-
- public boolean noVibrate() {
- return mNoVibrate;
- }
-
- public String getNotificationSoundUri() {
- return mNotificationSoundUri;
- }
- }
-
- SyncManager() {
- }
-
- /**
- * Timestamp of in progress sync - used to keep track of whether sync is running
- */
- private long mSyncInProgressTimestamp = -1;
-
- /**
- * Timestamp of current sync batch upper bound - used to determine if message makes batch dirty
- */
- private long mCurrentUpperBoundTimestamp = -1;
-
- /**
- * Timestamp of messages inserted since sync batch started - used to determine if batch dirty
- */
- private long mMaxRecentChangeTimestamp = -1L;
-
- private final ThreadInfoCache mThreadInfoCache = new ThreadInfoCache();
-
- /**
- * User customization to conversations. If this is set, we need to recover them after
- * a full sync.
- */
- private LongSparseArray<ConversationCustomization> mCustomization = null;
-
- /**
- * Start an incremental sync (backed off a few seconds)
- */
- public static void sync() {
- SyncMessagesAction.sync();
- }
-
- /**
- * Start an incremental sync (with no backoff)
- */
- public static void immediateSync() {
- SyncMessagesAction.immediateSync();
- }
-
- /**
- * Start a full sync (for debugging)
- */
- public static void forceSync() {
- SyncMessagesAction.fullSync();
- }
-
- /**
- * Called from data model thread when starting a sync batch
- * @param upperBoundTimestamp upper bound timestamp for sync batch
- */
- public synchronized void startSyncBatch(final long upperBoundTimestamp) {
- Assert.isTrue(mCurrentUpperBoundTimestamp < 0);
- mCurrentUpperBoundTimestamp = upperBoundTimestamp;
- mMaxRecentChangeTimestamp = -1L;
- }
-
- /**
- * Called from data model thread at end of batch to determine if any messages added in window
- * @param lowerBoundTimestamp lower bound timestamp for sync batch
- * @return true if message added within window from lower to upper bound timestamp of batch
- */
- public synchronized boolean isBatchDirty(final long lowerBoundTimestamp) {
- Assert.isTrue(mCurrentUpperBoundTimestamp >= 0);
- final long max = mMaxRecentChangeTimestamp;
-
- final boolean dirty = (max >= 0 && max >= lowerBoundTimestamp);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncManager: Sync batch of messages from " + lowerBoundTimestamp
- + " to " + mCurrentUpperBoundTimestamp + " is "
- + (dirty ? "DIRTY" : "clean") + "; max change timestamp = "
- + mMaxRecentChangeTimestamp);
- }
-
- mCurrentUpperBoundTimestamp = -1L;
- mMaxRecentChangeTimestamp = -1L;
-
- return dirty;
- }
-
- /**
- * Called from data model or background worker thread to indicate start of message add process
- * (add must complete on that thread before action transitions to new thread/stage)
- * @param timestamp timestamp of message being added
- */
- public synchronized void onNewMessageInserted(final long timestamp) {
- if (mCurrentUpperBoundTimestamp >= 0 && timestamp <= mCurrentUpperBoundTimestamp) {
- // Message insert in current sync window
- mMaxRecentChangeTimestamp = Math.max(mCurrentUpperBoundTimestamp, timestamp);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncManager: New message @ " + timestamp + " before upper bound of "
- + "current sync batch " + mCurrentUpperBoundTimestamp);
- }
- } else if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncManager: New message @ " + timestamp + " after upper bound of "
- + "current sync batch " + mCurrentUpperBoundTimestamp);
- }
- }
-
- /**
- * Synchronously checks whether sync is allowed and starts sync if allowed
- * @param full - true indicates a full (not incremental) sync operation
- * @param startTimestamp - starttimestamp for this sync (if allowed)
- * @return - true if sync should start
- */
- public synchronized boolean shouldSync(final boolean full, final long startTimestamp) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncManager: Checking shouldSync " + (full ? "full " : "")
- + "at " + startTimestamp);
- }
-
- if (full) {
- final long delayUntilFullSync = delayUntilFullSync(startTimestamp);
- if (delayUntilFullSync > 0) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncManager: Full sync requested for " + startTimestamp
- + " delayed for " + delayUntilFullSync + " ms");
- }
- return false;
- }
- }
-
- if (isSyncing()) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncManager: Not allowed to " + (full ? "full " : "")
- + "sync yet; still running sync started at " + mSyncInProgressTimestamp);
- }
- return false;
- }
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncManager: Starting " + (full ? "full " : "") + "sync at "
- + startTimestamp);
- }
-
- mSyncInProgressTimestamp = startTimestamp;
-
- return true;
- }
-
- /**
- * Return delay (in ms) until allowed to run a full sync (0 meaning can run immediately)
- * @param startTimestamp Timestamp used to start the sync
- * @return 0 if allowed to run now, else delay in ms
- */
- public long delayUntilFullSync(final long startTimestamp) {
- final BugleGservices bugleGservices = BugleGservices.get();
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
-
- final long lastFullSyncTime = prefs.getLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, -1L);
- final long smsFullSyncBackoffTimeMillis = bugleGservices.getLong(
- BugleGservicesKeys.SMS_FULL_SYNC_BACKOFF_TIME_MILLIS,
- BugleGservicesKeys.SMS_FULL_SYNC_BACKOFF_TIME_MILLIS_DEFAULT);
- final long noFullSyncBefore = (lastFullSyncTime < 0 ? startTimestamp :
- lastFullSyncTime + smsFullSyncBackoffTimeMillis);
-
- final long delayUntilFullSync = noFullSyncBefore - startTimestamp;
- if (delayUntilFullSync > 0) {
- return delayUntilFullSync;
- }
- return 0;
- }
-
- /**
- * Check if sync currently in progress (public for asserts/logging).
- */
- public synchronized boolean isSyncing() {
- return (mSyncInProgressTimestamp >= 0);
- }
-
- /**
- * Check if sync batch should be in progress - compares upperBound with in memory value
- * @param upperBoundTimestamp - upperbound timestamp for sync batch
- * @return - true if timestamps match (otherwise batch is orphan from older process)
- */
- public synchronized boolean isSyncing(final long upperBoundTimestamp) {
- Assert.isTrue(upperBoundTimestamp >= 0);
- return (upperBoundTimestamp == mCurrentUpperBoundTimestamp);
- }
-
- /**
- * Check if sync has completed for the first time.
- */
- public boolean getHasFirstSyncCompleted() {
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- return prefs.getLong(BuglePrefsKeys.LAST_SYNC_TIME,
- BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT) !=
- BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT;
- }
-
- /**
- * Called once sync is complete
- */
- public synchronized void complete() {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncManager: Sync started at " + mSyncInProgressTimestamp
- + " marked as complete");
- }
- mSyncInProgressTimestamp = -1L;
- // Conversation customization only used once
- mCustomization = null;
- }
-
- private final ContentObserver mMmsSmsObserver = new TelephonyMessagesObserver();
- private boolean mSyncOnChanges = false;
- private boolean mNotifyOnChanges = false;
-
- /**
- * Register content observer when necessary and kick off a catch up sync
- */
- public void updateSyncObserver(final Context context) {
- registerObserver(context);
- // Trigger an sms sync in case we missed and messages before registering this observer or
- // becoming the SMS provider.
- immediateSync();
- }
-
- private void registerObserver(final Context context) {
- if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
- // Not default SMS app - need to actively monitor telephony but not notify
- mNotifyOnChanges = false;
- mSyncOnChanges = true;
- } else if (OsUtil.isSecondaryUser()){
- // Secondary users default SMS app - need to actively monitor telephony and notify
- mNotifyOnChanges = true;
- mSyncOnChanges = true;
- } else {
- // Primary users default SMS app - don't monitor telephony (most changes from this app)
- mNotifyOnChanges = false;
- mSyncOnChanges = false;
- }
- if (mNotifyOnChanges || mSyncOnChanges) {
- context.getContentResolver().registerContentObserver(Telephony.MmsSms.CONTENT_URI,
- true, mMmsSmsObserver);
- } else {
- context.getContentResolver().unregisterContentObserver(mMmsSmsObserver);
- }
- }
-
- public synchronized void setCustomization(
- final LongSparseArray<ConversationCustomization> customization) {
- this.mCustomization = customization;
- }
-
- public synchronized ConversationCustomization getCustomizationForThread(final long threadId) {
- if (mCustomization != null) {
- return mCustomization.get(threadId);
- }
- return null;
- }
-
- public static void resetLastSyncTimestamps() {
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME,
- BuglePrefsKeys.LAST_FULL_SYNC_TIME_DEFAULT);
- prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT);
- }
-
- private class TelephonyMessagesObserver extends ContentObserver {
- public TelephonyMessagesObserver() {
- // Just run on default thread
- super(null);
- }
-
- // Implement the onChange(boolean) method to delegate the change notification to
- // the onChange(boolean, Uri) method to ensure correct operation on older versions
- // of the framework that did not have the onChange(boolean, Uri) method.
- @Override
- public void onChange(final boolean selfChange) {
- onChange(selfChange, null);
- }
-
- // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument.
- @Override
- public void onChange(final boolean selfChange, final Uri uri) {
- // Handle change.
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncManager: Sms/Mms DB changed @" + System.currentTimeMillis()
- + " for " + (uri == null ? "<unk>" : uri.toString()) + " "
- + mSyncOnChanges + "/" + mNotifyOnChanges);
- }
-
- if (mSyncOnChanges) {
- // If sync is already running this will do nothing - but at end of each sync
- // action there is a check for recent messages that should catch new changes.
- SyncManager.immediateSync();
- }
- if (mNotifyOnChanges) {
- // TODO: Secondary users are not going to get notifications
- }
- }
- }
-
- public ThreadInfoCache getThreadInfoCache() {
- return mThreadInfoCache;
- }
-
- public static class ThreadInfoCache {
- // Cache of thread->conversationId map
- private final LongSparseArray<String> mThreadToConversationId =
- new LongSparseArray<String>();
-
- // Cache of thread->recipients map
- private final LongSparseArray<List<String>> mThreadToRecipients =
- new LongSparseArray<List<String>>();
-
- // Remember the conversation ids that need to be archived
- private final HashSet<String> mArchivedConversations = new HashSet<>();
-
- public synchronized void clear() {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncManager: Cleared ThreadInfoCache");
- }
- mThreadToConversationId.clear();
- mThreadToRecipients.clear();
- mArchivedConversations.clear();
- }
-
- public synchronized boolean isArchived(final String conversationId) {
- return mArchivedConversations.contains(conversationId);
- }
-
- /**
- * Get or create a conversation based on the message's thread id
- *
- * @param threadId The message's thread
- * @param refSubId The subId used for normalizing phone numbers in the thread
- * @param customization The user setting customization to the conversation if any
- * @return The existing conversation id or new conversation id
- */
- public synchronized String getOrCreateConversation(final DatabaseWrapper db,
- final long threadId, int refSubId, final ConversationCustomization customization) {
- // This function has several components which need to be atomic.
- Assert.isTrue(db.getDatabase().inTransaction());
-
- // If we already have this conversation ID in our local map, just return it
- String conversationId = mThreadToConversationId.get(threadId);
- if (conversationId != null) {
- return conversationId;
- }
-
- final List<String> recipients = getThreadRecipients(threadId);
- final ArrayList<ParticipantData> participants =
- BugleDatabaseOperations.getConversationParticipantsFromRecipients(recipients,
- refSubId);
-
- if (customization != null) {
- // There is user customization we need to recover
- conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
- customization.isArchived(), participants, customization.isMuted(),
- customization.noVibrate(), customization.getNotificationSoundUri());
- if (customization.isArchived()) {
- mArchivedConversations.add(conversationId);
- }
- } else {
- conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
- false/*archived*/, participants, false/*noNotification*/,
- false/*noVibrate*/, null/*soundUri*/);
- }
-
- if (conversationId != null) {
- mThreadToConversationId.put(threadId, conversationId);
- return conversationId;
- }
-
- return null;
- }
-
-
- /**
- * Load the recipients of a thread from telephony provider. If we fail, use
- * a predefined unknown recipient. This should not return null.
- *
- * @param threadId
- */
- public synchronized List<String> getThreadRecipients(final long threadId) {
- List<String> recipients = mThreadToRecipients.get(threadId);
- if (recipients == null) {
- recipients = MmsUtils.getRecipientsByThread(threadId);
- if (recipients != null && recipients.size() > 0) {
- mThreadToRecipients.put(threadId, recipients);
- }
- }
-
- if (recipients == null || recipients.isEmpty()) {
- LogUtil.w(TAG, "SyncManager : using unknown sender since thread " + threadId +
- " couldn't find any recipients.");
-
- // We want to try our best to load the messages,
- // so if recipient info is broken, try to fix it with unknown recipient
- recipients = Lists.newArrayList();
- recipients.add(ParticipantData.getUnknownSenderDestination());
- }
-
- return recipients;
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/Action.java b/src/com/android/messaging/datamodel/action/Action.java
deleted file mode 100644
index e4c332e..0000000
--- a/src/com/android/messaging/datamodel/action/Action.java
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DataModelException;
-import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
-import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener;
-import com.android.messaging.util.LogUtil;
-
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Base class for operations that perform application business logic off the main UI thread while
- * holding a wake lock.
- * .
- * Note all derived classes need to provide real implementation of Parcelable (this is abstract)
- */
-public abstract class Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- // Members holding the parameters common to all actions - no action state
- public final String actionKey;
-
- // If derived classes keep their data in actionParameters then parcelable is trivial
- protected Bundle actionParameters;
-
- // This does not get written to the parcel
- private final List<Action> mBackgroundActions = new LinkedList<Action>();
-
- /**
- * Process the action locally - runs on action service thread.
- * TODO: Currently, there is no way for this method to indicate failure
- * @return result to be passed in to {@link ActionExecutedListener#onActionExecuted}. It is
- * also the result passed in to {@link ActionCompletedListener#onActionSucceeded} if
- * there is no background work.
- */
- protected Object executeAction() {
- return null;
- }
-
- /**
- * Queues up background work ie. {@link #doBackgroundWork} will be called on the
- * background worker thread.
- */
- protected void requestBackgroundWork() {
- mBackgroundActions.add(this);
- }
-
- /**
- * Queues up background actions for background processing after the current action has
- * completed its processing ({@link #executeAction}, {@link processBackgroundCompletion}
- * or {@link #processBackgroundFailure}) on the Action thread.
- * @param backgroundAction
- */
- protected void requestBackgroundWork(final Action backgroundAction) {
- mBackgroundActions.add(backgroundAction);
- }
-
- /**
- * Return flag indicating if any actions have been queued
- */
- public boolean hasBackgroundActions() {
- return !mBackgroundActions.isEmpty();
- }
-
- /**
- * Send queued actions to the background worker provided
- */
- public void sendBackgroundActions(final BackgroundWorker worker) {
- worker.queueBackgroundWork(mBackgroundActions);
- mBackgroundActions.clear();
- }
-
- /**
- * Do work in a long running background worker thread.
- * {@link #requestBackgroundWork} needs to be called for this method to
- * be called. {@link #processBackgroundFailure} will be called on the Action service thread
- * if this method throws {@link DataModelException}.
- * @return response that is to be passed to {@link #processBackgroundResponse}
- */
- protected Bundle doBackgroundWork() throws DataModelException {
- return null;
- }
-
- /**
- * Process the success response from the background worker. Runs on action service thread.
- * @param response the response returned by {@link #doBackgroundWork}
- * @return result to be passed in to {@link ActionCompletedListener#onActionSucceeded}
- */
- protected Object processBackgroundResponse(final Bundle response) {
- return null;
- }
-
- /**
- * Called in case of failures when sending background actions. Runs on action service thread
- * @return result to be passed in to {@link ActionCompletedListener#onActionFailed}
- */
- protected Object processBackgroundFailure() {
- return null;
- }
-
- /**
- * Constructor
- */
- protected Action(final String key) {
- this.actionKey = key;
- this.actionParameters = new Bundle();
- }
-
- /**
- * Constructor
- */
- protected Action() {
- this.actionKey = generateUniqueActionKey(getClass().getSimpleName());
- this.actionParameters = new Bundle();
- }
-
- /**
- * Queue an action and monitor for processing by the ActionService via the factory helper
- */
- protected void start(final ActionMonitor monitor) {
- ActionMonitor.registerActionMonitor(this.actionKey, monitor);
- DataModel.startActionService(this);
- }
-
- /**
- * Queue an action for processing by the ActionService via the factory helper
- */
- public void start() {
- DataModel.startActionService(this);
- }
-
- /**
- * Queue an action for delayed processing by the ActionService via the factory helper
- */
- public void schedule(final int requestCode, final long delayMs) {
- DataModel.scheduleAction(this, requestCode, delayMs);
- }
-
- /**
- * Called when action queues ActionService intent
- */
- protected final void markStart() {
- ActionMonitor.setState(this, ActionMonitor.STATE_CREATED,
- ActionMonitor.STATE_QUEUED);
- }
-
- /**
- * Mark the beginning of local action execution
- */
- protected final void markBeginExecute() {
- ActionMonitor.setState(this, ActionMonitor.STATE_QUEUED,
- ActionMonitor.STATE_EXECUTING);
- }
-
- /**
- * Mark the end of local action execution - either completes the action or queues
- * background actions
- */
- protected final void markEndExecute(final Object result) {
- final boolean hasBackgroundActions = hasBackgroundActions();
- ActionMonitor.setExecutedState(this, ActionMonitor.STATE_EXECUTING,
- hasBackgroundActions, result);
- if (!hasBackgroundActions) {
- ActionMonitor.setCompleteState(this, ActionMonitor.STATE_EXECUTING,
- result, true);
- }
- }
-
- /**
- * Update action state to indicate that the background worker is starting
- */
- protected final void markBackgroundWorkStarting() {
- ActionMonitor.setState(this,
- ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED,
- ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION);
- }
-
- /**
- * Update action state to indicate that the background worker has posted its response
- * (or failure) to the Action service
- */
- protected final void markBackgroundCompletionQueued() {
- ActionMonitor.setState(this,
- ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
- ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED);
- }
-
- /**
- * Update action state to indicate the background action failed but is being re-queued for retry
- */
- protected final void markBackgroundWorkQueued() {
- ActionMonitor.setState(this,
- ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
- ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED);
- }
-
- /**
- * Called by ActionService to process a response from the background worker
- * @param response the response returned by {@link #doBackgroundWork}
- */
- protected final void processBackgroundWorkResponse(final Bundle response) {
- ActionMonitor.setState(this,
- ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED,
- ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE);
- final Object result = processBackgroundResponse(response);
- ActionMonitor.setCompleteState(this,
- ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE, result, true);
- }
-
- /**
- * Called by ActionService when a background action fails
- */
- protected final void processBackgroundWorkFailure() {
- final Object result = processBackgroundFailure();
- ActionMonitor.setCompleteState(this, ActionMonitor.STATE_UNDEFINED,
- result, false);
- }
-
- private static final Object sLock = new Object();
- private static long sActionIdx = System.currentTimeMillis() * 1000;
-
- /**
- * Helper method to generate a unique operation index
- */
- protected static long getActionIdx() {
- long idx = 0;
- synchronized (sLock) {
- idx = ++sActionIdx;
- }
- return idx;
- }
-
- /**
- * This helper can be used to generate a unique key used to identify an action.
- * @param baseKey - key generated to identify the action parameters
- * @return - composite key generated by appending unique index
- */
- protected static String generateUniqueActionKey(final String baseKey) {
- final StringBuilder key = new StringBuilder();
- if (!TextUtils.isEmpty(baseKey)) {
- key.append(baseKey);
- }
- key.append(":");
- key.append(getActionIdx());
- return key.toString();
- }
-
- /**
- * Most derived classes use this base implementation (unless they include files handles)
- */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /**
- * Derived classes need to implement writeToParcel (but typically should call this method
- * to parcel Action member variables before they parcel their member variables).
- */
- public void writeActionToParcel(final Parcel parcel, final int flags) {
- parcel.writeString(this.actionKey);
- parcel.writeBundle(this.actionParameters);
- }
-
- /**
- * Helper for derived classes to implement parcelable
- */
- public Action(final Parcel in) {
- this.actionKey = in.readString();
- // Note: Need to set classloader to ensure we can un-parcel classes from this package
- this.actionParameters = in.readBundle(Action.class.getClassLoader());
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ActionMonitor.java b/src/com/android/messaging/datamodel/action/ActionMonitor.java
deleted file mode 100644
index cb080aa..0000000
--- a/src/com/android/messaging/datamodel/action/ActionMonitor.java
+++ /dev/null
@@ -1,477 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.os.Handler;
-import android.support.v4.util.SimpleArrayMap;
-import android.text.TextUtils;
-
-import com.android.messaging.util.Assert.RunsOnAnyThread;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.ThreadUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.TimeZone;
-
-/**
- * Base class for action monitors
- * Actions come in various flavors but
- * o) Fire and forget - no monitor
- * o) Immediate local processing only - will trigger ActionCompletedListener when done
- * o) Background worker processing only - will trigger ActionCompletedListener when done
- * o) Immediate local processing followed by background work followed by more local processing
- * - will trigger ActionExecutedListener once local processing complete and
- * ActionCompletedListener when second set of local process (dealing with background
- * worker response) is complete
- */
-public class ActionMonitor {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- /**
- * Interface used to notify on completion of local execution for an action
- */
- public interface ActionExecutedListener {
- /**
- * @param result value returned by {@link Action#executeAction}
- */
- @RunsOnMainThread
- abstract void onActionExecuted(ActionMonitor monitor, final Action action,
- final Object data, final Object result);
- }
-
- /**
- * Interface used to notify action completion
- */
- public interface ActionCompletedListener {
- /**
- * @param result object returned from processing the action. This is the value returned by
- * {@link Action#executeAction} if there is no background work, or
- * else the value returned by
- * {@link Action#processBackgroundResponse}
- */
- @RunsOnMainThread
- abstract void onActionSucceeded(ActionMonitor monitor,
- final Action action, final Object data, final Object result);
- /**
- * @param result value returned by {@link Action#processBackgroundFailure}
- */
- @RunsOnMainThread
- abstract void onActionFailed(ActionMonitor monitor, final Action action,
- final Object data, final Object result);
- }
-
- /**
- * Interface for being notified of action state changes - used for profiling, testing only
- */
- protected interface ActionStateChangedListener {
- /**
- * @param action the action that is changing state
- * @param state the new state of the action
- */
- @RunsOnAnyThread
- void onActionStateChanged(Action action, int state);
- }
-
- /**
- * Operations always start out as STATE_CREATED and finish as STATE_COMPLETE.
- * Some common state transition sequences in between include:
- * <ul>
- * <li>Local data change only : STATE_QUEUED - STATE_EXECUTING
- * <li>Background worker request only : STATE_BACKGROUND_ACTIONS_QUEUED
- * - STATE_EXECUTING_BACKGROUND_ACTION
- * - STATE_BACKGROUND_COMPLETION_QUEUED
- * - STATE_PROCESSING_BACKGROUND_RESPONSE
- * <li>Local plus background worker request : STATE_QUEUED - STATE_EXECUTING
- * - STATE_BACKGROUND_ACTIONS_QUEUED
- * - STATE_EXECUTING_BACKGROUND_ACTION
- * - STATE_BACKGROUND_COMPLETION_QUEUED
- * - STATE_PROCESSING_BACKGROUND_RESPONSE
- * </ul>
- */
- protected static final int STATE_UNDEFINED = 0;
- protected static final int STATE_CREATED = 1; // Just created
- protected static final int STATE_QUEUED = 2; // Action queued for processing
- protected static final int STATE_EXECUTING = 3; // Action processing on datamodel thread
- protected static final int STATE_BACKGROUND_ACTIONS_QUEUED = 4;
- protected static final int STATE_EXECUTING_BACKGROUND_ACTION = 5;
- // The background work has completed, either returning a success response or resulting in a
- // failure
- protected static final int STATE_BACKGROUND_COMPLETION_QUEUED = 6;
- protected static final int STATE_PROCESSING_BACKGROUND_RESPONSE = 7;
- protected static final int STATE_COMPLETE = 8; // Action complete
-
- /**
- * Lock used to protect access to state and listeners
- */
- private final Object mLock = new Object();
-
- /**
- * Current state of action
- */
- @VisibleForTesting
- protected int mState;
-
- /**
- * Listener which is notified on action completion
- */
- private ActionCompletedListener mCompletedListener;
-
- /**
- * Listener which is notified on action executed
- */
- private ActionExecutedListener mExecutedListener;
-
- /**
- * Listener which is notified of state changes
- */
- private ActionStateChangedListener mStateChangedListener;
-
- /**
- * Handler used to post results back to caller
- */
- private final Handler mHandler;
-
- /**
- * Data passed back to listeners (associated with the action when it is created)
- */
- private final Object mData;
-
- /**
- * The action key is used to determine equivalence of operations and their requests
- */
- private final String mActionKey;
-
- /**
- * Get action key identifying associated action
- */
- public String getActionKey() {
- return mActionKey;
- }
-
- /**
- * Unregister listeners so that they will not be called back - override this method if needed
- */
- public void unregister() {
- clearListeners();
- }
-
- /**
- * Unregister listeners so that they will not be called
- */
- protected final void clearListeners() {
- synchronized (mLock) {
- mCompletedListener = null;
- mExecutedListener = null;
- }
- }
-
- /**
- * Create a monitor associated with a particular action instance
- */
- protected ActionMonitor(final int initialState, final String actionKey,
- final Object data) {
- mHandler = ThreadUtil.getMainThreadHandler();
- mActionKey = actionKey;
- mState = initialState;
- mData = data;
- }
-
- /**
- * Return flag to indicate if action is complete
- */
- public boolean isComplete() {
- boolean complete = false;
- synchronized (mLock) {
- complete = (mState == STATE_COMPLETE);
- }
- return complete;
- }
-
- /**
- * Set listener that will be called with action completed result
- */
- protected final void setCompletedListener(final ActionCompletedListener listener) {
- synchronized (mLock) {
- mCompletedListener = listener;
- }
- }
-
- /**
- * Set listener that will be called with local execution result
- */
- protected final void setExecutedListener(final ActionExecutedListener listener) {
- synchronized (mLock) {
- mExecutedListener = listener;
- }
- }
-
- /**
- * Set listener that will be called with local execution result
- */
- protected final void setStateChangedListener(final ActionStateChangedListener listener) {
- synchronized (mLock) {
- mStateChangedListener = listener;
- }
- }
-
- /**
- * Perform a state update transition
- * @param action - action whose state is updating
- * @param expectedOldState - expected existing state of action (can be UNKNOWN)
- * @param newState - new state which will be set
- */
- @VisibleForTesting
- protected void updateState(final Action action, final int expectedOldState,
- final int newState) {
- ActionStateChangedListener listener = null;
- synchronized (mLock) {
- if (expectedOldState != STATE_UNDEFINED &&
- mState != expectedOldState) {
- throw new IllegalStateException("On updateState to " + newState + " was " + mState
- + " expecting " + expectedOldState);
- }
- if (newState != mState) {
- mState = newState;
- listener = mStateChangedListener;
- }
- }
- if (listener != null) {
- listener.onActionStateChanged(action, newState);
- }
- }
-
- /**
- * Perform a state update transition
- * @param action - action whose state is updating
- * @param expectedOldState - expected existing state of action (can be UNKNOWN)
- * @param newState - new state which will be set
- */
- static void setState(final Action action, final int expectedOldState,
- final int newState) {
- int oldMonitorState = expectedOldState;
- int newMonitorState = newState;
- final ActionMonitor monitor
- = ActionMonitor.lookupActionMonitor(action.actionKey);
- if (monitor != null) {
- oldMonitorState = monitor.mState;
- monitor.updateState(action, expectedOldState, newState);
- newMonitorState = monitor.mState;
- }
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
- df.setTimeZone(TimeZone.getTimeZone("UTC"));
- LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
- + "UTC State = " + oldMonitorState + " - " + newMonitorState);
- }
- }
-
- /**
- * Mark action complete
- * @param action - action whose state is updating
- * @param expectedOldState - expected existing state of action (can be UNKNOWN)
- * @param result - object returned from processing the action. This is the value returned by
- * {@link Action#executeAction} if there is no background work, or
- * else the value returned by {@link Action#processBackgroundResponse}
- * or {@link Action#processBackgroundFailure}
- */
- private final void complete(final Action action,
- final int expectedOldState, final Object result,
- final boolean succeeded) {
- ActionCompletedListener completedListener = null;
- synchronized (mLock) {
- setState(action, expectedOldState, STATE_COMPLETE);
- completedListener = mCompletedListener;
- mExecutedListener = null;
- mStateChangedListener = null;
- }
- if (completedListener != null) {
- // Marshal to UI thread
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- ActionCompletedListener listener = null;
- synchronized (mLock) {
- if (mCompletedListener != null) {
- listener = mCompletedListener;
- }
- mCompletedListener = null;
- }
- if (listener != null) {
- if (succeeded) {
- listener.onActionSucceeded(ActionMonitor.this,
- action, mData, result);
- } else {
- listener.onActionFailed(ActionMonitor.this,
- action, mData, result);
- }
- }
- }
- });
- }
- }
-
- /**
- * Mark action complete
- * @param action - action whose state is updating
- * @param expectedOldState - expected existing state of action (can be UNKNOWN)
- * @param result - object returned from processing the action. This is the value returned by
- * {@link Action#executeAction} if there is no background work, or
- * else the value returned by {@link Action#processBackgroundResponse}
- * or {@link Action#processBackgroundFailure}
- */
- static void setCompleteState(final Action action, final int expectedOldState,
- final Object result, final boolean succeeded) {
- int oldMonitorState = expectedOldState;
- final ActionMonitor monitor
- = ActionMonitor.lookupActionMonitor(action.actionKey);
- if (monitor != null) {
- oldMonitorState = monitor.mState;
- monitor.complete(action, expectedOldState, result, succeeded);
- unregisterActionMonitorIfComplete(action.actionKey, monitor);
- }
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
- df.setTimeZone(TimeZone.getTimeZone("UTC"));
- LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
- + "UTC State = " + oldMonitorState + " - " + STATE_COMPLETE);
- }
- }
-
- /**
- * Mark action complete
- * @param action - action whose state is updating
- * @param expectedOldState - expected existing state of action (can be UNKNOWN)
- * @param hasBackgroundActions - has the completing action requested background work
- * @param result - the return value of {@link Action#executeAction}
- */
- final void executed(final Action action,
- final int expectedOldState, final boolean hasBackgroundActions, final Object result) {
- ActionExecutedListener executedListener = null;
- synchronized (mLock) {
- if (hasBackgroundActions) {
- setState(action, expectedOldState, STATE_BACKGROUND_ACTIONS_QUEUED);
- }
- executedListener = mExecutedListener;
- }
- if (executedListener != null) {
- // Marshal to UI thread
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- ActionExecutedListener listener = null;
- synchronized (mLock) {
- if (mExecutedListener != null) {
- listener = mExecutedListener;
- mExecutedListener = null;
- }
- }
- if (listener != null) {
- listener.onActionExecuted(ActionMonitor.this,
- action, mData, result);
- }
- }
- });
- }
- }
-
- /**
- * Mark action complete
- * @param action - action whose state is updating
- * @param expectedOldState - expected existing state of action (can be UNKNOWN)
- * @param hasBackgroundActions - has the completing action requested background work
- * @param result - the return value of {@link Action#executeAction}
- */
- static void setExecutedState(final Action action,
- final int expectedOldState, final boolean hasBackgroundActions, final Object result) {
- int oldMonitorState = expectedOldState;
- final ActionMonitor monitor
- = ActionMonitor.lookupActionMonitor(action.actionKey);
- if (monitor != null) {
- oldMonitorState = monitor.mState;
- monitor.executed(action, expectedOldState, hasBackgroundActions, result);
- }
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
- df.setTimeZone(TimeZone.getTimeZone("UTC"));
- LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
- + "UTC State = " + oldMonitorState + " - EXECUTED");
- }
- }
-
- /**
- * Map of action monitors indexed by actionKey
- */
- @VisibleForTesting
- static SimpleArrayMap<String, ActionMonitor> sActionMonitors =
- new SimpleArrayMap<String, ActionMonitor>();
-
- /**
- * Insert new monitor into map
- */
- static void registerActionMonitor(final String actionKey,
- final ActionMonitor monitor) {
- if (monitor != null
- && (TextUtils.isEmpty(monitor.getActionKey())
- || TextUtils.isEmpty(actionKey)
- || !actionKey.equals(monitor.getActionKey()))) {
- throw new IllegalArgumentException("Monitor key " + monitor.getActionKey()
- + " not compatible with action key " + actionKey);
- }
- synchronized (sActionMonitors) {
- sActionMonitors.put(actionKey, monitor);
- }
- }
-
- /**
- * Find monitor associated with particular action
- */
- private static ActionMonitor lookupActionMonitor(final String actionKey) {
- ActionMonitor monitor = null;
- synchronized (sActionMonitors) {
- monitor = sActionMonitors.get(actionKey);
- }
- return monitor;
- }
-
- /**
- * Remove monitor from map
- */
- @VisibleForTesting
- static void unregisterActionMonitor(final String actionKey,
- final ActionMonitor monitor) {
- if (monitor != null) {
- synchronized (sActionMonitors) {
- sActionMonitors.remove(actionKey);
- }
- }
- }
-
- /**
- * Remove monitor from map if the action is complete
- */
- static void unregisterActionMonitorIfComplete(final String actionKey,
- final ActionMonitor monitor) {
- if (monitor != null && monitor.isComplete()) {
- synchronized (sActionMonitors) {
- sActionMonitors.remove(actionKey);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ActionService.java b/src/com/android/messaging/datamodel/action/ActionService.java
deleted file mode 100644
index 29225fa..0000000
--- a/src/com/android/messaging/datamodel/action/ActionService.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.os.Bundle;
-
-/**
- * Class providing interface for the ActionService - can be stubbed for testing
- */
-public class ActionService {
- protected static PendingIntent makeStartActionPendingIntent(final Context context,
- final Action action, final int requestCode, final boolean launchesAnActivity) {
- return ActionServiceImpl.makeStartActionPendingIntent(context, action, requestCode,
- launchesAnActivity);
- }
-
- /**
- * Start an action by posting it over the the ActionService
- */
- public void startAction(final Action action) {
- ActionServiceImpl.startAction(action);
- }
-
- /**
- * Schedule a delayed action by posting it over the the ActionService
- */
- public void scheduleAction(final Action action, final int code,
- final long delayMs) {
- ActionServiceImpl.scheduleAction(action, code, delayMs);
- }
-
- /**
- * Process a response from the BackgroundWorker in the ActionService
- */
- protected void handleResponseFromBackgroundWorker(
- final Action action, final Bundle response) {
- ActionServiceImpl.handleResponseFromBackgroundWorker(action, response);
- }
-
- /**
- * Process a failure from the BackgroundWorker in the ActionService
- */
- protected void handleFailureFromBackgroundWorker(final Action action,
- final Exception exception) {
- ActionServiceImpl.handleFailureFromBackgroundWorker(action, exception);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ActionServiceImpl.java b/src/com/android/messaging/datamodel/action/ActionServiceImpl.java
deleted file mode 100644
index a408dac..0000000
--- a/src/com/android/messaging/datamodel/action/ActionServiceImpl.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.app.AlarmManager;
-import android.app.IntentService;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.LoggingTimer;
-import com.android.messaging.util.WakeLockHelper;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * ActionService used to perform background processing for data model
- */
-public class ActionServiceImpl extends IntentService {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- private static final boolean VERBOSE = false;
-
- public ActionServiceImpl() {
- super("ActionService");
- }
-
- /**
- * Start action by sending intent to the service
- * @param action - action to start
- */
- protected static void startAction(final Action action) {
- final Intent intent = makeIntent(OP_START_ACTION);
- final Bundle actionBundle = new Bundle();
- actionBundle.putParcelable(BUNDLE_ACTION, action);
- intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
- action.markStart();
- startServiceWithIntent(intent);
- }
-
- /**
- * Schedule an action to run after specified delay using alarm manager to send pendingintent
- * @param action - action to start
- * @param requestCode - request code used to collapse requests
- * @param delayMs - delay in ms (from now) before action will start
- */
- protected static void scheduleAction(final Action action, final int requestCode,
- final long delayMs) {
- final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION);
- final Bundle actionBundle = new Bundle();
- actionBundle.putParcelable(BUNDLE_ACTION, action);
- intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
-
- PendingActionReceiver.scheduleAlarm(intent, requestCode, delayMs);
- }
-
- /**
- * Handle response returned by BackgroundWorker
- * @param request - request generating response
- * @param response - response from service
- */
- protected static void handleResponseFromBackgroundWorker(final Action action,
- final Bundle response) {
- final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_RESPONSE);
-
- final Bundle actionBundle = new Bundle();
- actionBundle.putParcelable(BUNDLE_ACTION, action);
- intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
- intent.putExtra(EXTRA_WORKER_RESPONSE, response);
-
- startServiceWithIntent(intent);
- }
-
- /**
- * Handle response returned by BackgroundWorker
- * @param request - request generating failure
- */
- protected static void handleFailureFromBackgroundWorker(final Action action,
- final Exception exception) {
- final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_FAILURE);
-
- final Bundle actionBundle = new Bundle();
- actionBundle.putParcelable(BUNDLE_ACTION, action);
- intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
- intent.putExtra(EXTRA_WORKER_EXCEPTION, exception);
-
- startServiceWithIntent(intent);
- }
-
- // ops
- @VisibleForTesting
- protected static final int OP_START_ACTION = 200;
- @VisibleForTesting
- protected static final int OP_RECEIVE_BACKGROUND_RESPONSE = 201;
- @VisibleForTesting
- protected static final int OP_RECEIVE_BACKGROUND_FAILURE = 202;
-
- // extras
- @VisibleForTesting
- protected static final String EXTRA_OP_CODE = "op";
- @VisibleForTesting
- protected static final String EXTRA_ACTION_BUNDLE = "datamodel_action_bundle";
- @VisibleForTesting
- protected static final String EXTRA_WORKER_EXCEPTION = "worker_exception";
- @VisibleForTesting
- protected static final String EXTRA_WORKER_RESPONSE = "worker_response";
- @VisibleForTesting
- protected static final String EXTRA_WORKER_UPDATE = "worker_update";
- @VisibleForTesting
- protected static final String BUNDLE_ACTION = "bundle_action";
-
- private BackgroundWorker mBackgroundWorker;
-
- /**
- * Allocate an intent with a specific opcode.
- */
- private static Intent makeIntent(final int opcode) {
- final Intent intent = new Intent(Factory.get().getApplicationContext(),
- ActionServiceImpl.class);
- intent.putExtra(EXTRA_OP_CODE, opcode);
- return intent;
- }
-
- /**
- * Broadcast receiver for alarms scheduled through ActionService.
- */
- public static class PendingActionReceiver extends BroadcastReceiver {
- static final String ACTION = "com.android.messaging.datamodel.PENDING_ACTION";
-
- /**
- * Allocate an intent with a specific opcode and alarm action.
- */
- public static Intent makeIntent(final int opcode) {
- final Intent intent = new Intent(Factory.get().getApplicationContext(),
- PendingActionReceiver.class);
- intent.setAction(ACTION);
- intent.putExtra(EXTRA_OP_CODE, opcode);
- return intent;
- }
-
- public static void scheduleAlarm(final Intent intent, final int requestCode,
- final long delayMs) {
- final Context context = Factory.get().getApplicationContext();
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
-
- final AlarmManager mgr =
- (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
- if (delayMs < Long.MAX_VALUE) {
- mgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + delayMs, pendingIntent);
- } else {
- mgr.cancel(pendingIntent);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onReceive(final Context context, final Intent intent) {
- ActionServiceImpl.startServiceWithIntent(intent);
- }
- }
-
- /**
- * Creates a pending intent that will trigger a data model action when the intent is
- * triggered
- */
- public static PendingIntent makeStartActionPendingIntent(final Context context,
- final Action action, final int requestCode, final boolean launchesAnActivity) {
- final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION);
- final Bundle actionBundle = new Bundle();
- actionBundle.putParcelable(BUNDLE_ACTION, action);
- intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
- if (launchesAnActivity) {
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- }
- return PendingIntent.getBroadcast(context, requestCode, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onCreate() {
- super.onCreate();
- mBackgroundWorker = DataModel.get().getBackgroundWorkerForActionService();
- DataModel.get().getConnectivityUtil().registerForSignalStrength();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- DataModel.get().getConnectivityUtil().unregisterForSignalStrength();
- }
-
- private static final String WAKELOCK_ID = "bugle_datamodel_service_wakelock";
- @VisibleForTesting
- static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID);
-
- /**
- * Queue intent to the ActionService after acquiring wake lock
- */
- private static void startServiceWithIntent(final Intent intent) {
- final Context context = Factory.get().getApplicationContext();
- final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
- // Increase refCount on wake lock - acquiring if necessary
- if (VERBOSE) {
- LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode);
- }
- sWakeLock.acquire(context, intent, opcode);
- intent.setClass(context, ActionServiceImpl.class);
-
- // TODO: Note that intent will be quietly discarded if it exceeds available rpc
- // memory (in total around 1MB). See this article for background
- // http://developer.android.com/reference/android/os/TransactionTooLargeException.html
- // Perhaps we should keep large structures in the action monitor?
- if (context.startService(intent) == null) {
- LogUtil.e(TAG,
- "ActionService.startServiceWithIntent: failed to start service for intent "
- + intent);
- sWakeLock.release(intent, opcode);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onHandleIntent(final Intent intent) {
- if (intent == null) {
- // Shouldn't happen but sometimes does following another crash.
- LogUtil.w(TAG, "ActionService.onHandleIntent: Called with null intent");
- return;
- }
- final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
- sWakeLock.ensure(intent, opcode);
-
- try {
- Action action;
- final Bundle actionBundle = intent.getBundleExtra(EXTRA_ACTION_BUNDLE);
- actionBundle.setClassLoader(getClassLoader());
- switch(opcode) {
- case OP_START_ACTION: {
- action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
- executeAction(action);
- break;
- }
-
- case OP_RECEIVE_BACKGROUND_RESPONSE: {
- action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
- final Bundle response = intent.getBundleExtra(EXTRA_WORKER_RESPONSE);
- processBackgroundResponse(action, response);
- break;
- }
-
- case OP_RECEIVE_BACKGROUND_FAILURE: {
- action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
- processBackgroundFailure(action);
- break;
- }
-
- default:
- throw new RuntimeException("Unrecognized opcode in ActionServiceImpl");
- }
-
- action.sendBackgroundActions(mBackgroundWorker);
- } finally {
- // Decrease refCount on wake lock - releasing if necessary
- sWakeLock.release(intent, opcode);
- }
- }
-
- private static final long EXECUTION_TIME_WARN_LIMIT_MS = 1000; // 1 second
- /**
- * Local execution of action on ActionService thread
- */
- private void executeAction(final Action action) {
- action.markBeginExecute();
-
- final LoggingTimer timer = createLoggingTimer(action, "#executeAction");
- timer.start();
-
- final Object result = action.executeAction();
-
- timer.stopAndLog();
-
- action.markEndExecute(result);
- }
-
- /**
- * Process response on ActionService thread
- */
- private void processBackgroundResponse(final Action action, final Bundle response) {
- final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundResponse");
- timer.start();
-
- action.processBackgroundWorkResponse(response);
-
- timer.stopAndLog();
- }
-
- /**
- * Process failure on ActionService thread
- */
- private void processBackgroundFailure(final Action action) {
- final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundFailure");
- timer.start();
-
- action.processBackgroundWorkFailure();
-
- timer.stopAndLog();
- }
-
- private static LoggingTimer createLoggingTimer(
- final Action action, final String methodName) {
- return new LoggingTimer(TAG, action.getClass().getSimpleName() + methodName,
- EXECUTION_TIME_WARN_LIMIT_MS);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/BackgroundWorker.java b/src/com/android/messaging/datamodel/action/BackgroundWorker.java
deleted file mode 100644
index aad3c07..0000000
--- a/src/com/android/messaging/datamodel/action/BackgroundWorker.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import java.util.List;
-
-/**
- * Interface between action service and its workers
- */
-public class BackgroundWorker {
-
- /**
- * Send list of requests from action service to a worker
- */
- public void queueBackgroundWork(final List<Action> backgroundActions) {
- BackgroundWorkerService.queueBackgroundWork(backgroundActions);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java b/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java
deleted file mode 100644
index 4d4b150..0000000
--- a/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.app.IntentService;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DataModelException;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.LoggingTimer;
-import com.android.messaging.util.WakeLockHelper;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.List;
-
-/**
- * Background worker service is an initial example of a background work queue handler
- * Used to actually "send" messages which may take some time and should not block ActionService
- * or UI
- */
-public class BackgroundWorkerService extends IntentService {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- private static final boolean VERBOSE = false;
-
- private static final String WAKELOCK_ID = "bugle_background_worker_wakelock";
- @VisibleForTesting
- static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID);
-
- private final ActionService mHost;
-
- public BackgroundWorkerService() {
- super("BackgroundWorker");
- mHost = DataModel.get().getActionService();
- }
-
- /**
- * Queue a list of requests from action service to this worker
- */
- public static void queueBackgroundWork(final List<Action> actions) {
- for (final Action action : actions) {
- startServiceWithAction(action, 0);
- }
- }
-
- // ops
- @VisibleForTesting
- protected static final int OP_PROCESS_REQUEST = 400;
-
- // extras
- @VisibleForTesting
- protected static final String EXTRA_OP_CODE = "op";
- @VisibleForTesting
- protected static final String EXTRA_ACTION = "action";
- @VisibleForTesting
- protected static final String EXTRA_ATTEMPT = "retry_attempt";
-
- /**
- * Queue action intent to the BackgroundWorkerService after acquiring wake lock
- */
- private static void startServiceWithAction(final Action action,
- final int retryCount) {
- final Intent intent = new Intent();
- intent.putExtra(EXTRA_ACTION, action);
- intent.putExtra(EXTRA_ATTEMPT, retryCount);
- startServiceWithIntent(OP_PROCESS_REQUEST, intent);
- }
-
- /**
- * Queue intent to the BackgroundWorkerService after acquiring wake lock
- */
- private static void startServiceWithIntent(final int opcode, final Intent intent) {
- final Context context = Factory.get().getApplicationContext();
-
- intent.setClass(context, BackgroundWorkerService.class);
- intent.putExtra(EXTRA_OP_CODE, opcode);
- sWakeLock.acquire(context, intent, opcode);
- if (VERBOSE) {
- LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode);
- }
-
- if (context.startService(intent) == null) {
- LogUtil.e(TAG,
- "BackgroundWorkerService.startServiceWithAction: failed to start service for "
- + opcode);
- sWakeLock.release(intent, opcode);
- }
- }
-
- @Override
- protected void onHandleIntent(final Intent intent) {
- if (intent == null) {
- // Shouldn't happen but sometimes does following another crash.
- LogUtil.w(TAG, "BackgroundWorkerService.onHandleIntent: Called with null intent");
- return;
- }
- final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
- sWakeLock.ensure(intent, opcode);
-
- try {
- switch(opcode) {
- case OP_PROCESS_REQUEST: {
- final Action action = intent.getParcelableExtra(EXTRA_ACTION);
- final int attempt = intent.getIntExtra(EXTRA_ATTEMPT, -1);
- doBackgroundWork(action, attempt);
- break;
- }
-
- default:
- throw new RuntimeException("Unrecognized opcode in BackgroundWorkerService");
- }
- } finally {
- sWakeLock.release(intent, opcode);
- }
- }
-
- /**
- * Local execution of background work for action on ActionService thread
- */
- private void doBackgroundWork(final Action action, final int attempt) {
- action.markBackgroundWorkStarting();
- Bundle response = null;
- try {
- final LoggingTimer timer = new LoggingTimer(
- TAG, action.getClass().getSimpleName() + "#doBackgroundWork");
- timer.start();
-
- response = action.doBackgroundWork();
-
- timer.stopAndLog();
- action.markBackgroundCompletionQueued();
- mHost.handleResponseFromBackgroundWorker(action, response);
- } catch (final Exception exception) {
- final boolean retry = false;
- LogUtil.e(TAG, "Error in background worker", exception);
- if (!(exception instanceof DataModelException)) {
- // DataModelException is expected (sort-of) and handled in handleFailureFromWorker
- // below, but other exceptions should crash ENG builds
- Assert.fail("Unexpected error in background worker - abort");
- }
- if (retry) {
- action.markBackgroundWorkQueued();
- startServiceWithAction(action, attempt + 1);
- } else {
- action.markBackgroundCompletionQueued();
- mHost.handleFailureFromBackgroundWorker(action, exception);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/BugleActionToasts.java b/src/com/android/messaging/datamodel/action/BugleActionToasts.java
deleted file mode 100644
index f60facd..0000000
--- a/src/com/android/messaging/datamodel/action/BugleActionToasts.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.action;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.widget.Toast;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.ThreadUtil;
-
-import javax.annotation.Nullable;
-
-/**
- * Shows one-time, transient notifications in response to action failures (i.e. permanent failures
- * when sending a message) by showing toasts.
- */
-public class BugleActionToasts {
- /**
- * Called when SendMessageAction or DownloadMmsAction finishes
- * @param conversationId the conversation of the sent or downloaded message
- * @param success did the action succeed
- * @param status the message sending status
- * @param isSms whether the message is sent using SMS
- * @param subId the subId of the SIM related to this send
- * @param isSend whether it is a send (false for download)
- */
- static void onSendMessageOrManualDownloadActionCompleted(
- final String conversationId,
- final boolean success,
- final int status,
- final boolean isSms,
- final int subId,
- final boolean isSend) {
- // We only show notifications for two cases, i.e. when mobile data is off or when we are
- // in airplane mode, both of which fail fast with permanent failures.
- if (!success && status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) {
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- if (phoneUtils.isAirplaneModeOn()) {
- if (isSend) {
- showToast(R.string.send_message_failure_airplane_mode);
- } else {
- showToast(R.string.download_message_failure_airplane_mode);
- }
- return;
- } else if (!isSms && !phoneUtils.isMobileDataEnabled()) {
- if (isSend) {
- showToast(R.string.send_message_failure_no_data);
- } else {
- showToast(R.string.download_message_failure_no_data);
- }
- return;
- }
- }
-
- if (AccessibilityUtil.isTouchExplorationEnabled(Factory.get().getApplicationContext())) {
- final boolean isFocusedConversation = DataModel.get().isFocusedConversation(conversationId);
- if (isFocusedConversation && success) {
- // Using View.announceForAccessibility may be preferable, but we do not have a
- // View, and so we use a toast instead.
- showToast(isSend ? R.string.send_message_success
- : R.string.download_message_success);
- return;
- }
-
- // {@link MessageNotificationState#checkFailedMessages} does not post a notification for
- // failures in observable conversations. For accessibility, we provide an indication
- // here.
- final boolean isObservableConversation = DataModel.get().isNewMessageObservable(
- conversationId);
- if (isObservableConversation && !success) {
- showToast(isSend ? R.string.send_message_failure
- : R.string.download_message_failure);
- }
- }
- }
-
- public static void onMessageReceived(final String conversationId,
- @Nullable final ParticipantData sender, @Nullable final MessageData message) {
- final Context context = Factory.get().getApplicationContext();
- if (AccessibilityUtil.isTouchExplorationEnabled(context)) {
- final boolean isFocusedConversation = DataModel.get().isFocusedConversation(
- conversationId);
- if (isFocusedConversation) {
- final Resources res = context.getResources();
- final String senderDisplayName = (sender == null)
- ? res.getString(R.string.unknown_sender) : sender.getDisplayName(false);
- final String announcement = res.getString(
- R.string.incoming_message_announcement, senderDisplayName,
- (message == null) ? "" : message.getMessageText());
- showToast(announcement);
- }
- }
- }
-
- public static void onConversationDeleted() {
- showToast(R.string.conversation_deleted);
- }
-
- private static void showToast(final int messageResId) {
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(getApplicationContext(),
- getApplicationContext().getString(messageResId), Toast.LENGTH_LONG).show();
- }
- });
- }
-
- private static void showToast(final String message) {
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
- }
- });
- }
-
- private static Context getApplicationContext() {
- return Factory.get().getApplicationContext();
- }
-
- private static class UpdateDestinationBlockedActionToast
- implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener {
- private final Context mContext;
-
- UpdateDestinationBlockedActionToast(final Context context) {
- mContext = context;
- }
-
- @Override
- public void onUpdateDestinationBlockedAction(
- final UpdateDestinationBlockedAction action,
- final boolean success,
- final boolean block,
- final String destination) {
- if (success) {
- Toast.makeText(mContext,
- block
- ? R.string.update_destination_blocked
- : R.string.update_destination_unblocked,
- Toast.LENGTH_LONG
- ).show();
- }
- }
- }
-
- public static UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
- makeUpdateDestinationBlockedActionListener(final Context context) {
- return new UpdateDestinationBlockedActionToast(context);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/DeleteConversationAction.java b/src/com/android/messaging/datamodel/action/DeleteConversationAction.java
deleted file mode 100644
index a00f6d6..0000000
--- a/src/com/android/messaging/datamodel/action/DeleteConversationAction.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DataModelException;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.widget.WidgetConversationProvider;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Action used to delete a conversation.
- */
-public class DeleteConversationAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- public static void deleteConversation(final String conversationId, final long cutoffTimestamp) {
- final DeleteConversationAction action = new DeleteConversationAction(conversationId,
- cutoffTimestamp);
- action.start();
- }
-
- private static final String KEY_CONVERSATION_ID = "conversation_id";
- private static final String KEY_CUTOFF_TIMESTAMP = "cutoff_timestamp";
-
- private DeleteConversationAction(final String conversationId, final long cutoffTimestamp) {
- super();
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- // TODO: Should we set cuttoff timestamp to prevent us deleting new messages?
- actionParameters.putLong(KEY_CUTOFF_TIMESTAMP, cutoffTimestamp);
- }
-
- // Delete conversation from both the local DB and telephony in the background so sync cannot
- // run concurrently and incorrectly try to recreate the conversation's messages locally. The
- // telephony database can sometimes be quite slow to delete conversations, so we delete from
- // the local DB first, notify the UI, and then delete from telephony.
- @Override
- protected Bundle doBackgroundWork() throws DataModelException {
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- final long cutoffTimestamp = actionParameters.getLong(KEY_CUTOFF_TIMESTAMP);
-
- if (!TextUtils.isEmpty(conversationId)) {
- // First find the thread id for this conversation.
- final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId);
-
- if (BugleDatabaseOperations.deleteConversation(db, conversationId, cutoffTimestamp)) {
- LogUtil.i(TAG, "DeleteConversationAction: Deleted local conversation "
- + conversationId);
-
- BugleActionToasts.onConversationDeleted();
-
- // Remove notifications if necessary
- BugleNotifications.update(true /* silent */, null /* conversationId */,
- BugleNotifications.UPDATE_MESSAGES);
-
- // We have changed the conversation list
- MessagingContentProvider.notifyConversationListChanged();
-
- // Notify the widget the conversation is deleted so it can go into its configure state.
- WidgetConversationProvider.notifyConversationDeleted(
- Factory.get().getApplicationContext(),
- conversationId);
- } else {
- LogUtil.w(TAG, "DeleteConversationAction: Could not delete local conversation "
- + conversationId);
- return null;
- }
-
- // Now delete from telephony DB. MmsSmsProvider throws an exception if the thread id is
- // less than 0. If it's greater than zero, it will delete all messages with that thread
- // id, even if there's no corresponding row in the threads table.
- if (threadId >= 0) {
- final int count = MmsUtils.deleteThread(threadId, cutoffTimestamp);
- if (count > 0) {
- LogUtil.i(TAG, "DeleteConversationAction: Deleted telephony thread "
- + threadId + " (cutoffTimestamp = " + cutoffTimestamp + ")");
- } else {
- LogUtil.w(TAG, "DeleteConversationAction: Could not delete thread from "
- + "telephony: conversationId = " + conversationId + ", thread id = "
- + threadId);
- }
- } else {
- LogUtil.w(TAG, "DeleteConversationAction: Local conversation " + conversationId
- + " has an invalid telephony thread id; will delete messages individually");
- deleteConversationMessagesFromTelephony();
- }
- } else {
- LogUtil.e(TAG, "DeleteConversationAction: conversationId is empty");
- }
-
- return null;
- }
-
- /**
- * Deletes all the telephony messages for the local conversation being deleted.
- * <p>
- * This is a fallback used when the conversation is not associated with any telephony thread,
- * or its thread id is invalid (e.g. negative). This is not common, but can happen sometimes
- * (e.g. the Unknown Sender conversation). In the usual case of deleting a conversation, we
- * don't need this because the telephony provider automatically deletes messages when a thread
- * is deleted.
- */
- private void deleteConversationMessagesFromTelephony() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- Assert.notNull(conversationId);
-
- final List<Uri> messageUris = new ArrayList<>();
- Cursor cursor = null;
- try {
- cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
- new String[] { MessageColumns.SMS_MESSAGE_URI },
- MessageColumns.CONVERSATION_ID + "=?",
- new String[] { conversationId },
- null, null, null);
- while (cursor.moveToNext()) {
- String messageUri = cursor.getString(0);
- try {
- messageUris.add(Uri.parse(messageUri));
- } catch (Exception e) {
- LogUtil.e(TAG, "DeleteConversationAction: Could not parse message uri "
- + messageUri);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- for (Uri messageUri : messageUris) {
- int count = MmsUtils.deleteMessage(messageUri);
- if (count > 0) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "DeleteConversationAction: Deleted telephony message "
- + messageUri);
- }
- } else {
- LogUtil.w(TAG, "DeleteConversationAction: Could not delete telephony message "
- + messageUri);
- }
- }
- }
-
- @Override
- protected Object executeAction() {
- requestBackgroundWork();
- return null;
- }
-
- private DeleteConversationAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<DeleteConversationAction> CREATOR
- = new Parcelable.Creator<DeleteConversationAction>() {
- @Override
- public DeleteConversationAction createFromParcel(final Parcel in) {
- return new DeleteConversationAction(in);
- }
-
- @Override
- public DeleteConversationAction[] newArray(final int size) {
- return new DeleteConversationAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/DeleteMessageAction.java b/src/com/android/messaging/datamodel/action/DeleteMessageAction.java
deleted file mode 100644
index 9ddb2a6..0000000
--- a/src/com/android/messaging/datamodel/action/DeleteMessageAction.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Action used to delete a single message.
- */
-public class DeleteMessageAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- public static void deleteMessage(final String messageId) {
- final DeleteMessageAction action = new DeleteMessageAction(messageId);
- action.start();
- }
-
- private static final String KEY_MESSAGE_ID = "message_id";
-
- private DeleteMessageAction(final String messageId) {
- super();
- actionParameters.putString(KEY_MESSAGE_ID, messageId);
- }
-
- // Doing this work in the background so that we're not competing with sync
- // which could bring the deleted message back to life between the time we deleted
- // it locally and deleted it in telephony (sync is also done on doBackgroundWork).
- //
- // Previously this block of code deleted from telephony first but that can be very
- // slow (on the order of seconds) so this was modified to first delete locally, trigger
- // the UI update, then delete from telephony.
- @Override
- protected Bundle doBackgroundWork() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- // First find the thread id for this conversation.
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
-
- if (!TextUtils.isEmpty(messageId)) {
- // Check message still exists
- final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
- if (message != null) {
- // Delete from local DB
- int count = BugleDatabaseOperations.deleteMessage(db, messageId);
- if (count > 0) {
- LogUtil.i(TAG, "DeleteMessageAction: Deleted local message "
- + messageId);
- } else {
- LogUtil.w(TAG, "DeleteMessageAction: Could not delete local message "
- + messageId);
- }
- MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
- // We may have changed the conversation list
- MessagingContentProvider.notifyConversationListChanged();
-
- final Uri messageUri = message.getSmsMessageUri();
- if (messageUri != null) {
- // Delete from telephony DB
- count = MmsUtils.deleteMessage(messageUri);
- if (count > 0) {
- LogUtil.i(TAG, "DeleteMessageAction: Deleted telephony message "
- + messageUri);
- } else {
- LogUtil.w(TAG, "DeleteMessageAction: Could not delete message from "
- + "telephony: messageId = " + messageId + ", telephony uri = "
- + messageUri);
- }
- } else {
- LogUtil.i(TAG, "DeleteMessageAction: Local message " + messageId
- + " has no telephony uri.");
- }
- } else {
- LogUtil.w(TAG, "DeleteMessageAction: Message " + messageId + " no longer exists");
- }
- }
- return null;
- }
-
- /**
- * Delete the message.
- */
- @Override
- protected Object executeAction() {
- requestBackgroundWork();
- return null;
- }
-
- private DeleteMessageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<DeleteMessageAction> CREATOR
- = new Parcelable.Creator<DeleteMessageAction>() {
- @Override
- public DeleteMessageAction createFromParcel(final Parcel in) {
- return new DeleteMessageAction(in);
- }
-
- @Override
- public DeleteMessageAction[] newArray(final int size) {
- return new DeleteMessageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/DownloadMmsAction.java b/src/com/android/messaging/datamodel/action/DownloadMmsAction.java
deleted file mode 100644
index 7a8c907..0000000
--- a/src/com/android/messaging/datamodel/action/DownloadMmsAction.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Downloads an MMS message.
- * <p>
- * This class is public (not package-private) because the SMS/MMS (e.g. MmsUtils) classes need to
- * access the EXTRA_* fields for setting up the 'downloaded' pending intent.
- */
-public class DownloadMmsAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- /**
- * Interface for DownloadMmsAction listeners
- */
- public interface DownloadMmsActionListener {
- @RunsOnMainThread
- abstract void onDownloadMessageStarting(final ActionMonitor monitor,
- final Object data, final MessageData message);
- @RunsOnMainThread
- abstract void onDownloadMessageSucceeded(final ActionMonitor monitor,
- final Object data, final MessageData message);
- @RunsOnMainThread
- abstract void onDownloadMessageFailed(final ActionMonitor monitor,
- final Object data, final MessageData message);
- }
-
- /**
- * Queue download of an mms notification message (can only be called during execute of action)
- */
- static boolean queueMmsForDownloadInBackground(final String messageId,
- final Action processingAction) {
- // When this method is being called, it is always from auto download
- final DownloadMmsAction action = new DownloadMmsAction();
- // This could queue nothing
- return action.queueAction(messageId, processingAction);
- }
-
- private static final String KEY_MESSAGE_ID = "message_id";
- private static final String KEY_CONVERSATION_ID = "conversation_id";
- private static final String KEY_PARTICIPANT_ID = "participant_id";
- private static final String KEY_CONTENT_LOCATION = "content_location";
- private static final String KEY_TRANSACTION_ID = "transaction_id";
- private static final String KEY_NOTIFICATION_URI = "notification_uri";
- private static final String KEY_SUB_ID = "sub_id";
- private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
- private static final String KEY_AUTO_DOWNLOAD = "auto_download";
- private static final String KEY_FAILURE_STATUS = "failure_status";
-
- // Values we attach to the pending intent that's fired when the message is downloaded.
- // Only applicable when downloading via the platform APIs on L+.
- public static final String EXTRA_MESSAGE_ID = "message_id";
- public static final String EXTRA_CONTENT_URI = "content_uri";
- public static final String EXTRA_NOTIFICATION_URI = "notification_uri";
- public static final String EXTRA_SUB_ID = "sub_id";
- public static final String EXTRA_SUB_PHONE_NUMBER = "sub_phone_number";
- public static final String EXTRA_TRANSACTION_ID = "transaction_id";
- public static final String EXTRA_CONTENT_LOCATION = "content_location";
- public static final String EXTRA_AUTO_DOWNLOAD = "auto_download";
- public static final String EXTRA_RECEIVED_TIMESTAMP = "received_timestamp";
- public static final String EXTRA_CONVERSATION_ID = "conversation_id";
- public static final String EXTRA_PARTICIPANT_ID = "participant_id";
- public static final String EXTRA_STATUS_IF_FAILED = "status_if_failed";
-
- private DownloadMmsAction() {
- super();
- }
-
- @Override
- protected Object executeAction() {
- Assert.fail("DownloadMmsAction must be queued rather than started");
- return null;
- }
-
- protected boolean queueAction(final String messageId, final Action processingAction) {
- actionParameters.putString(KEY_MESSAGE_ID, messageId);
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
- // Read the message from local db
- final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
- if (message != null && message.canDownloadMessage()) {
- final Uri notificationUri = message.getSmsMessageUri();
- final String conversationId = message.getConversationId();
- final int status = message.getStatus();
-
- final String selfId = message.getSelfId();
- final ParticipantData self = BugleDatabaseOperations
- .getExistingParticipant(db, selfId);
- final int subId = self.getSubId();
- actionParameters.putInt(KEY_SUB_ID, subId);
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- actionParameters.putString(KEY_PARTICIPANT_ID, message.getParticipantId());
- actionParameters.putString(KEY_CONTENT_LOCATION, message.getMmsContentLocation());
- actionParameters.putString(KEY_TRANSACTION_ID, message.getMmsTransactionId());
- actionParameters.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
- actionParameters.putBoolean(KEY_AUTO_DOWNLOAD, isAutoDownload(status));
-
- final long now = System.currentTimeMillis();
- if (message.getInDownloadWindow(now)) {
- // We can still retry
- actionParameters.putString(KEY_SUB_PHONE_NUMBER, self.getNormalizedDestination());
-
- final int downloadingStatus = getDownloadingStatus(status);
- // Update message status to indicate downloading.
- updateMessageStatus(notificationUri, messageId, conversationId,
- downloadingStatus, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED);
- // Pre-compute the next status when failed so we don't have to load from db again
- actionParameters.putInt(KEY_FAILURE_STATUS, getFailureStatus(downloadingStatus));
-
- // Actual download happens in background
- processingAction.requestBackgroundWork(this);
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG,
- "DownloadMmsAction: Queued download of MMS message " + messageId);
- }
- return true;
- } else {
- LogUtil.w(TAG, "DownloadMmsAction: Download of MMS message " + messageId
- + " failed (outside download window)");
-
- // Retries depleted and we failed. Update the message status so we won't retry again
- updateMessageStatus(notificationUri, messageId, conversationId,
- MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED,
- MessageData.RAW_TELEPHONY_STATUS_UNDEFINED);
- if (status == MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD) {
- // For auto download failure, we should send a DEFERRED NotifyRespInd
- // to carrier to indicate we will manual download later
- ProcessDownloadedMmsAction.sendDeferredRespStatus(
- messageId, message.getMmsTransactionId(),
- message.getMmsContentLocation(), subId);
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Find out the auto download state of this message based on its starting status
- *
- * @param status The starting status of the message.
- * @return True if this is a message doing auto downloading, false otherwise
- */
- private static boolean isAutoDownload(final int status) {
- switch (status) {
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
- return false;
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
- return true;
- default:
- Assert.fail("isAutoDownload: invalid input status " + status);
- return false;
- }
- }
-
- /**
- * Get the corresponding downloading status based on the starting status of the message
- *
- * @param status The starting status of the message.
- * @return The downloading status
- */
- private static int getDownloadingStatus(final int status) {
- switch (status) {
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
- return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING;
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
- return MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING;
- default:
- Assert.fail("isAutoDownload: invalid input status " + status);
- return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING;
- }
- }
-
- /**
- * Get the corresponding failed status based on the current downloading status
- *
- * @param status The downloading status
- * @return The status the message should have if downloading failed
- */
- private static int getFailureStatus(final int status) {
- switch (status) {
- case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
- return MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD;
- case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
- return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD;
- default:
- Assert.fail("isAutoDownload: invalid input status " + status);
- return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD;
- }
- }
-
- @Override
- protected Bundle doBackgroundWork() {
- final Context context = Factory.get().getApplicationContext();
- final int subId = actionParameters.getInt(KEY_SUB_ID);
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
- final Uri notificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI);
- final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER);
- final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
- final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
- final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD);
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID);
- final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS);
-
- final long receivedTimestampRoundedToSecond =
- 1000 * ((System.currentTimeMillis() + 500) / 1000);
-
- LogUtil.i(TAG, "DownloadMmsAction: Downloading MMS message " + messageId
- + " (" + (autoDownload ? "auto" : "manual") + ")");
-
- // Bundle some values we'll need after the message is downloaded (via platform APIs)
- final Bundle extras = new Bundle();
- extras.putString(EXTRA_MESSAGE_ID, messageId);
- extras.putString(EXTRA_CONVERSATION_ID, conversationId);
- extras.putString(EXTRA_PARTICIPANT_ID, participantId);
- extras.putInt(EXTRA_STATUS_IF_FAILED, statusIfFailed);
-
- // Start the download
- final MmsUtils.StatusPlusUri status = MmsUtils.downloadMmsMessage(context,
- notificationUri, subId, subPhoneNumber, transactionId, contentLocation,
- autoDownload, receivedTimestampRoundedToSecond / 1000L, extras);
- if (status == MmsUtils.STATUS_PENDING) {
- // Async download; no status yet
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "DownloadMmsAction: Downloading MMS message " + messageId
- + " asynchronously; waiting for pending intent to signal completion");
- }
- } else {
- // Inform sync that message has been added at local received timestamp
- final SyncManager syncManager = DataModel.get().getSyncManager();
- syncManager.onNewMessageInserted(receivedTimestampRoundedToSecond);
- // Handle downloaded message
- ProcessDownloadedMmsAction.processMessageDownloadFastFailed(messageId,
- notificationUri, conversationId, participantId, contentLocation, subId,
- subPhoneNumber, statusIfFailed, autoDownload, transactionId,
- status.resultCode);
- }
- return null;
- }
-
- @Override
- protected Object processBackgroundResponse(final Bundle response) {
- // Nothing to do here; post-download actions handled by ProcessDownloadedMmsAction
- return null;
- }
-
- @Override
- protected Object processBackgroundFailure() {
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
- final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID);
- final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS);
- final int subId = actionParameters.getInt(KEY_SUB_ID);
-
- ProcessDownloadedMmsAction.processDownloadActionFailure(messageId,
- MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
- conversationId, participantId, statusIfFailed, subId, transactionId);
-
- return null;
- }
-
- static void updateMessageStatus(final Uri messageUri, final String messageId,
- final String conversationId, final int status, final int rawStatus) {
- final Context context = Factory.get().getApplicationContext();
- // Downloading status just kept in local DB but need to fix up telephony DB first
- if (status == MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING ||
- status == MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING) {
- MmsUtils.clearMmsStatus(context, messageUri);
- }
- // Then mark downloading status in our local DB
- final ContentValues values = new ContentValues();
- values.put(MessageColumns.STATUS, status);
- values.put(MessageColumns.RAW_TELEPHONY_STATUS, rawStatus);
- final DatabaseWrapper db = DataModel.get().getDatabase();
- BugleDatabaseOperations.updateMessageRowIfExists(db, messageId, values);
-
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- }
-
- private DownloadMmsAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<DownloadMmsAction> CREATOR
- = new Parcelable.Creator<DownloadMmsAction>() {
- @Override
- public DownloadMmsAction createFromParcel(final Parcel in) {
- return new DownloadMmsAction(in);
- }
-
- @Override
- public DownloadMmsAction[] newArray(final int size) {
- return new DownloadMmsAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java b/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java
deleted file mode 100644
index ab320bf..0000000
--- a/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.LogUtil;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-public class DumpDatabaseAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- public static final String DUMP_NAME = "db_copy.db";
- private static final int BUFFER_SIZE = 16384;
-
- /**
- * Copy the database to external storage
- */
- public static void dumpDatabase() {
- final DumpDatabaseAction action = new DumpDatabaseAction();
- action.start();
- }
-
- private DumpDatabaseAction() {
- }
-
- @Override
- protected Object executeAction() {
- final Context context = Factory.get().getApplicationContext();
- final String dbName = DatabaseHelper.DATABASE_NAME;
- BufferedOutputStream bos = null;
- BufferedInputStream bis = null;
-
- long originalSize = 0;
- final File inFile = context.getDatabasePath(dbName);
- if (inFile.exists() && inFile.isFile()) {
- originalSize = inFile.length();
- }
- final File outFile = DebugUtils.getDebugFile(DUMP_NAME, true);
- if (outFile != null) {
- int totalBytes = 0;
- try {
- bos = new BufferedOutputStream(new FileOutputStream(outFile));
- bis = new BufferedInputStream(new FileInputStream(inFile));
-
- final byte[] buffer = new byte[BUFFER_SIZE];
- int bytesRead;
- while ((bytesRead = bis.read(buffer)) > 0) {
- bos.write(buffer, 0, bytesRead);
- totalBytes += bytesRead;
- }
- } catch (final IOException e) {
- LogUtil.w(TAG, "Exception copying the database;"
- + " destination may not be complete.", e);
- } finally {
- if (bos != null) {
- try {
- bos.close();
- } catch (final IOException e) {
- // Nothing to do
- }
- }
-
- if (bis != null) {
- try {
- bis.close();
- } catch (final IOException e) {
- // Nothing to do
- }
- }
- DebugUtils.ensureReadable(outFile);
- LogUtil.i(TAG, "Dump complete; orig size: " + originalSize +
- ", copy size: " + totalBytes);
- }
- }
- return null;
- }
-
- private DumpDatabaseAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<DumpDatabaseAction> CREATOR
- = new Parcelable.Creator<DumpDatabaseAction>() {
- @Override
- public DumpDatabaseAction createFromParcel(final Parcel in) {
- return new DumpDatabaseAction(in);
- }
-
- @Override
- public DumpDatabaseAction[] newArray(final int size) {
- return new DumpDatabaseAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java b/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java
deleted file mode 100644
index e3d131d..0000000
--- a/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Action used to fixup actively downloading or sending status at startup - just in case we
- * crash - never run this when a message might actually be sending or downloading.
- */
-public class FixupMessageStatusOnStartupAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- public static void fixupMessageStatus() {
- final FixupMessageStatusOnStartupAction action = new FixupMessageStatusOnStartupAction();
- action.start();
- }
-
- private FixupMessageStatusOnStartupAction() {
- }
-
- @Override
- protected Object executeAction() {
- // Now mark any messages in active sending or downloading state as inactive
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- int downloadFailedCnt = 0;
- int sendFailedCnt = 0;
- try {
- // For both sending and downloading messages, let's assume they failed.
- // For MMS sent/downloaded via platform, the sent/downloaded pending intent
- // may come back. That will update the message. User may see the message
- // in wrong status within a short window if that happens. But this should
- // rarely happen. This is a simple solution to situations like app gets killed
- // while the pending intent is still in the fly. Alternatively, we could
- // keep the status for platform sent/downloaded MMS and timeout these messages.
- // But that is much more complex.
- final ContentValues values = new ContentValues();
- values.put(DatabaseHelper.MessageColumns.STATUS,
- MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED);
- downloadFailedCnt += db.update(DatabaseHelper.MESSAGES_TABLE, values,
- DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
- new String[]{
- Integer.toString(MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING),
- Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING)
- });
- values.clear();
-
- values.clear();
- values.put(DatabaseHelper.MessageColumns.STATUS,
- MessageData.BUGLE_STATUS_OUTGOING_FAILED);
- sendFailedCnt = db.update(DatabaseHelper.MESSAGES_TABLE, values,
- DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
- new String[]{
- Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_SENDING),
- Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING)
- });
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- LogUtil.i(TAG, "Fixup: Send failed - " + sendFailedCnt
- + " Download failed - " + downloadFailedCnt);
-
- // Don't send contentObserver notifications as displayed text should not change
- return null;
- }
-
- private FixupMessageStatusOnStartupAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<FixupMessageStatusOnStartupAction> CREATOR
- = new Parcelable.Creator<FixupMessageStatusOnStartupAction>() {
- @Override
- public FixupMessageStatusOnStartupAction createFromParcel(final Parcel in) {
- return new FixupMessageStatusOnStartupAction(in);
- }
-
- @Override
- public FixupMessageStatusOnStartupAction[] newArray(final int size) {
- return new FixupMessageStatusOnStartupAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java b/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java
deleted file mode 100644
index b262141..0000000
--- a/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
-import com.android.messaging.datamodel.data.LaunchConversationData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-
-/**
- * Action used to get or create a conversation for a list of conversation participants.
- */
-public class GetOrCreateConversationAction extends Action implements Parcelable {
- /**
- * Interface for GetOrCreateConversationAction listeners
- */
- public interface GetOrCreateConversationActionListener {
- @RunsOnMainThread
- abstract void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
- final Object data, final String conversationId);
-
- @RunsOnMainThread
- abstract void onGetOrCreateConversationFailed(final ActionMonitor monitor,
- final Object data);
- }
-
- public static GetOrCreateConversationActionMonitor getOrCreateConversation(
- final ArrayList<ParticipantData> participants, final Object data,
- final GetOrCreateConversationActionListener listener) {
- final GetOrCreateConversationActionMonitor monitor = new
- GetOrCreateConversationActionMonitor(data, listener);
- final GetOrCreateConversationAction action = new GetOrCreateConversationAction(participants,
- monitor.getActionKey());
- action.start(monitor);
- return monitor;
- }
-
-
- public static GetOrCreateConversationActionMonitor getOrCreateConversation(
- final String[] recipients, final Object data, final LaunchConversationData listener) {
- final ArrayList<ParticipantData> participants = new ArrayList<>();
- for (String recipient : recipients) {
- recipient = recipient.trim();
- if (!TextUtils.isEmpty(recipient)) {
- participants.add(ParticipantData.getFromRawPhoneBySystemLocale(recipient));
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "getOrCreateConversation hit empty recipient");
- }
- }
- return getOrCreateConversation(participants, data, listener);
- }
-
- private static final String KEY_PARTICIPANTS_LIST = "participants_list";
-
- private GetOrCreateConversationAction(final ArrayList<ParticipantData> participants,
- final String actionKey) {
- super(actionKey);
- actionParameters.putParcelableArrayList(KEY_PARTICIPANTS_LIST, participants);
- }
-
- /**
- * Lookup the conversation or create a new one.
- */
- @Override
- protected Object executeAction() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- // First find the thread id for this list of participants.
- final ArrayList<ParticipantData> participants =
- actionParameters.getParcelableArrayList(KEY_PARTICIPANTS_LIST);
- BugleDatabaseOperations.sanitizeConversationParticipants(participants);
- final ArrayList<String> recipients =
- BugleDatabaseOperations.getRecipientsFromConversationParticipants(participants);
-
- final long threadId = MmsUtils.getOrCreateThreadId(Factory.get().getApplicationContext(),
- recipients);
-
- if (threadId < 0) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't create a threadId in SMS db for numbers : " +
- LogUtil.sanitizePII(recipients.toString()));
- // TODO: Add a better way to indicate an error from executeAction.
- return null;
- }
-
- final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
- false, participants, false, false, null);
-
- return conversationId;
- }
-
- /**
- * A monitor that notifies a listener upon completion
- */
- public static class GetOrCreateConversationActionMonitor extends ActionMonitor
- implements ActionCompletedListener {
- private final GetOrCreateConversationActionListener mListener;
-
- GetOrCreateConversationActionMonitor(final Object data,
- final GetOrCreateConversationActionListener listener) {
- super(STATE_CREATED, generateUniqueActionKey("GetOrCreateConversationAction"), data);
- setCompletedListener(this);
- mListener = listener;
- }
-
- @Override
- public void onActionSucceeded(final ActionMonitor monitor,
- final Action action, final Object data, final Object result) {
- if (result == null) {
- mListener.onGetOrCreateConversationFailed(monitor, data);
- } else {
- mListener.onGetOrCreateConversationSucceeded(monitor, data, (String) result);
- }
- }
-
- @Override
- public void onActionFailed(final ActionMonitor monitor,
- final Action action, final Object data, final Object result) {
- // TODO: Currently onActionFailed is only called if there is an error in
- // processing requests, not for errors in the local processing.
- Assert.fail("Unreachable");
- mListener.onGetOrCreateConversationFailed(monitor, data);
- }
- }
-
- private GetOrCreateConversationAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<GetOrCreateConversationAction> CREATOR
- = new Parcelable.Creator<GetOrCreateConversationAction>() {
- @Override
- public GetOrCreateConversationAction createFromParcel(final Parcel in) {
- return new GetOrCreateConversationAction(in);
- }
-
- @Override
- public GetOrCreateConversationAction[] newArray(final int size) {
- return new GetOrCreateConversationAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java b/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java
deleted file mode 100644
index 7bfcfe0..0000000
--- a/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.sms.SmsReleaseStorage;
-import com.android.messaging.util.Assert;
-
-/**
- * Action used to handle low storage related issues on the device.
- */
-public class HandleLowStorageAction extends Action implements Parcelable {
- private static final int SUB_OP_CODE_CLEAR_MEDIA_MESSAGES = 100;
- private static final int SUB_OP_CODE_CLEAR_OLD_MESSAGES = 101;
-
- public static void handleDeleteMediaMessages(final long durationInMillis) {
- final HandleLowStorageAction action = new HandleLowStorageAction(
- SUB_OP_CODE_CLEAR_MEDIA_MESSAGES, durationInMillis);
- action.start();
- }
-
- public static void handleDeleteOldMessages(final long durationInMillis) {
- final HandleLowStorageAction action = new HandleLowStorageAction(
- SUB_OP_CODE_CLEAR_OLD_MESSAGES, durationInMillis);
- action.start();
- }
-
- private static final String KEY_SUB_OP_CODE = "sub_op_code";
- private static final String KEY_CUTOFF_DURATION_MILLIS = "cutoff_duration_millis";
-
- private HandleLowStorageAction(final int subOpcode, final long durationInMillis) {
- super();
- actionParameters.putInt(KEY_SUB_OP_CODE, subOpcode);
- actionParameters.putLong(KEY_CUTOFF_DURATION_MILLIS, durationInMillis);
- }
-
- @Override
- protected Object executeAction() {
- final int subOpCode = actionParameters.getInt(KEY_SUB_OP_CODE);
- final long durationInMillis = actionParameters.getLong(KEY_CUTOFF_DURATION_MILLIS);
- switch (subOpCode) {
- case SUB_OP_CODE_CLEAR_MEDIA_MESSAGES:
- SmsReleaseStorage.deleteMessages(0, durationInMillis);
- break;
-
- case SUB_OP_CODE_CLEAR_OLD_MESSAGES:
- SmsReleaseStorage.deleteMessages(1, durationInMillis);
- break;
-
- default:
- Assert.fail("Unsupported action type!");
- break;
- }
- return true;
- }
-
- private HandleLowStorageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<HandleLowStorageAction> CREATOR
- = new Parcelable.Creator<HandleLowStorageAction>() {
- @Override
- public HandleLowStorageAction createFromParcel(final Parcel in) {
- return new HandleLowStorageAction(in);
- }
-
- @Override
- public HandleLowStorageAction[] newArray(final int size) {
- return new HandleLowStorageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java b/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java
deleted file mode 100644
index 2567ca9..0000000
--- a/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java
+++ /dev/null
@@ -1,480 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Action used to convert a draft message to an outgoing message. Its writes SMS messages to
- * the telephony db, but {@link SendMessageAction} is responsible for inserting MMS message into
- * the telephony DB. The latter also does the actual sending of the message in the background.
- * The latter is also responsible for re-sending a failed message.
- */
-public class InsertNewMessageAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- private static long sLastSentMessageTimestamp = -1;
-
- /**
- * Insert message (no listener)
- */
- public static void insertNewMessage(final MessageData message) {
- final InsertNewMessageAction action = new InsertNewMessageAction(message);
- action.start();
- }
-
- /**
- * Insert message (no listener) with a given non-default subId.
- */
- public static void insertNewMessage(final MessageData message, final int subId) {
- Assert.isFalse(subId == ParticipantData.DEFAULT_SELF_SUB_ID);
- final InsertNewMessageAction action = new InsertNewMessageAction(message, subId);
- action.start();
- }
-
- /**
- * Insert message (no listener)
- */
- public static void insertNewMessage(final int subId, final String recipients,
- final String messageText, final String subject) {
- final InsertNewMessageAction action = new InsertNewMessageAction(
- subId, recipients, messageText, subject);
- action.start();
- }
-
- public static long getLastSentMessageTimestamp() {
- return sLastSentMessageTimestamp;
- }
-
- private static final String KEY_SUB_ID = "sub_id";
- private static final String KEY_MESSAGE = "message";
- private static final String KEY_RECIPIENTS = "recipients";
- private static final String KEY_MESSAGE_TEXT = "message_text";
- private static final String KEY_SUBJECT_TEXT = "subject_text";
-
- private InsertNewMessageAction(final MessageData message) {
- this(message, ParticipantData.DEFAULT_SELF_SUB_ID);
- actionParameters.putParcelable(KEY_MESSAGE, message);
- }
-
- private InsertNewMessageAction(final MessageData message, final int subId) {
- super();
- actionParameters.putParcelable(KEY_MESSAGE, message);
- actionParameters.putInt(KEY_SUB_ID, subId);
- }
-
- private InsertNewMessageAction(final int subId, final String recipients,
- final String messageText, final String subject) {
- super();
- if (TextUtils.isEmpty(recipients) || TextUtils.isEmpty(messageText)) {
- Assert.fail("InsertNewMessageAction: Can't have empty recipients or message");
- }
- actionParameters.putInt(KEY_SUB_ID, subId);
- actionParameters.putString(KEY_RECIPIENTS, recipients);
- actionParameters.putString(KEY_MESSAGE_TEXT, messageText);
- actionParameters.putString(KEY_SUBJECT_TEXT, subject);
- }
-
- /**
- * Add message to database in pending state and queue actual sending
- */
- @Override
- protected Object executeAction() {
- LogUtil.i(TAG, "InsertNewMessageAction: inserting new message");
- MessageData message = actionParameters.getParcelable(KEY_MESSAGE);
- if (message == null) {
- LogUtil.i(TAG, "InsertNewMessageAction: Creating MessageData with provided data");
- message = createMessage();
- if (message == null) {
- LogUtil.w(TAG, "InsertNewMessageAction: Could not create MessageData");
- return null;
- }
- }
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final String conversationId = message.getConversationId();
-
- final ParticipantData self = getSelf(db, conversationId, message);
- if (self == null) {
- return null;
- }
- message.bindSelfId(self.getId());
- // If the user taps the Send button before the conversation draft is created/loaded by
- // ReadDraftDataAction (maybe the action service thread was busy), the MessageData may not
- // have the participant id set. It should be equal to the self id, so we'll use that.
- if (message.getParticipantId() == null) {
- message.bindParticipantId(self.getId());
- }
-
- final long timestamp = System.currentTimeMillis();
- final ArrayList<String> recipients =
- BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
- if (recipients.size() < 1) {
- LogUtil.w(TAG, "InsertNewMessageAction: message recipients is empty");
- return null;
- }
- final int subId = self.getSubId();
-
- // TODO: Work out whether to send with SMS or MMS (taking into account recipients)?
- final boolean isSms = (message.getProtocol() == MessageData.PROTOCOL_SMS);
- if (isSms) {
- String sendingConversationId = conversationId;
- if (recipients.size() > 1) {
- // Broadcast SMS - put message in "fake conversation" before farming out to real 1:1
- final long laterTimestamp = timestamp + 1;
- // Send a single message
- insertBroadcastSmsMessage(conversationId, message, subId,
- laterTimestamp, recipients);
-
- sendingConversationId = null;
- }
-
- for (final String recipient : recipients) {
- // Start actual sending
- insertSendingSmsMessage(message, subId, recipient,
- timestamp, sendingConversationId);
- }
-
- // Can now clear draft from conversation (deleting attachments if necessary)
- BugleDatabaseOperations.updateDraftMessageData(db, conversationId,
- null /* message */, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT);
- } else {
- final long timestampRoundedToSecond = 1000 * ((timestamp + 500) / 1000);
- // Write place holder message directly referencing parts from the draft
- final MessageData messageToSend = insertSendingMmsMessage(conversationId,
- message, timestampRoundedToSecond);
-
- // Can now clear draft from conversation (preserving attachments which are now
- // referenced by messageToSend)
- BugleDatabaseOperations.updateDraftMessageData(db, conversationId,
- messageToSend, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT);
- }
- MessagingContentProvider.notifyConversationListChanged();
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
-
- return message;
- }
-
- private ParticipantData getSelf(
- final DatabaseWrapper db, final String conversationId, final MessageData message) {
- ParticipantData self;
- // Check if we are asked to bind to a non-default subId. This is directly passed in from
- // the UI thread so that the sub id may be locked as soon as the user clicks on the Send
- // button.
- final int requestedSubId = actionParameters.getInt(
- KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
- if (requestedSubId != ParticipantData.DEFAULT_SELF_SUB_ID) {
- self = BugleDatabaseOperations.getOrCreateSelf(db, requestedSubId);
- } else {
- String selfId = message.getSelfId();
- if (selfId == null) {
- // The conversation draft provides no self id hint, meaning that 1) conversation
- // self id was not loaded AND 2) the user didn't pick a SIM from the SIM selector.
- // In this case, use the conversation's self id.
- final ConversationListItemData conversation =
- ConversationListItemData.getExistingConversation(db, conversationId);
- if (conversation != null) {
- selfId = conversation.getSelfId();
- } else {
- LogUtil.w(LogUtil.BUGLE_DATAMODEL_TAG, "Conversation " + conversationId +
- "already deleted before sending draft message " +
- message.getMessageId() + ". Aborting InsertNewMessageAction.");
- return null;
- }
- }
-
- // We do not use SubscriptionManager.DEFAULT_SUB_ID for sending a message, so we need
- // to bind the message to the system default subscription if it's unbound.
- final ParticipantData unboundSelf = BugleDatabaseOperations.getExistingParticipant(
- db, selfId);
- if (unboundSelf.getSubId() == ParticipantData.DEFAULT_SELF_SUB_ID
- && OsUtil.isAtLeastL_MR1()) {
- final int defaultSubId = PhoneUtils.getDefault().getDefaultSmsSubscriptionId();
- self = BugleDatabaseOperations.getOrCreateSelf(db, defaultSubId);
- } else {
- self = unboundSelf;
- }
- }
- return self;
- }
-
- /** Create MessageData using KEY_RECIPIENTS, KEY_MESSAGE_TEXT and KEY_SUBJECT */
- private MessageData createMessage() {
- // First find the thread id for this list of participants.
- final String recipientsList = actionParameters.getString(KEY_RECIPIENTS);
- final String messageText = actionParameters.getString(KEY_MESSAGE_TEXT);
- final String subjectText = actionParameters.getString(KEY_SUBJECT_TEXT);
- final int subId = actionParameters.getInt(
- KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
-
- final ArrayList<ParticipantData> participants = new ArrayList<>();
- for (final String recipient : recipientsList.split(",")) {
- participants.add(ParticipantData.getFromRawPhoneBySimLocale(recipient, subId));
- }
- if (participants.size() == 0) {
- Assert.fail("InsertNewMessage: Empty participants");
- return null;
- }
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
- BugleDatabaseOperations.sanitizeConversationParticipants(participants);
- final ArrayList<String> recipients =
- BugleDatabaseOperations.getRecipientsFromConversationParticipants(participants);
- if (recipients.size() == 0) {
- Assert.fail("InsertNewMessage: Empty recipients");
- return null;
- }
-
- final long threadId = MmsUtils.getOrCreateThreadId(Factory.get().getApplicationContext(),
- recipients);
-
- if (threadId < 0) {
- Assert.fail("InsertNewMessage: Couldn't get threadId in SMS db for these recipients: "
- + recipients.toString());
- // TODO: How do we fail the action?
- return null;
- }
-
- final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
- false, participants, false, false, null);
-
- final ParticipantData self = BugleDatabaseOperations.getOrCreateSelf(db, subId);
-
- if (TextUtils.isEmpty(subjectText)) {
- return MessageData.createDraftSmsMessage(conversationId, self.getId(), messageText);
- } else {
- return MessageData.createDraftMmsMessage(conversationId, self.getId(), messageText,
- subjectText);
- }
- }
-
- private void insertBroadcastSmsMessage(final String conversationId,
- final MessageData message, final int subId, final long laterTimestamp,
- final ArrayList<String> recipients) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "InsertNewMessageAction: Inserting broadcast SMS message "
- + message.getMessageId());
- }
- final Context context = Factory.get().getApplicationContext();
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- // Inform sync that message is being added at timestamp
- final SyncManager syncManager = DataModel.get().getSyncManager();
- syncManager.onNewMessageInserted(laterTimestamp);
-
- final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId);
- final String address = TextUtils.join(" ", recipients);
-
- final String messageText = message.getMessageText();
- // Insert message into telephony database sms message table
- final Uri messageUri = MmsUtils.insertSmsMessage(context,
- Telephony.Sms.CONTENT_URI,
- subId,
- address,
- messageText,
- laterTimestamp,
- Telephony.Sms.STATUS_COMPLETE,
- Telephony.Sms.MESSAGE_TYPE_SENT, threadId);
- if (messageUri != null && !TextUtils.isEmpty(messageUri.toString())) {
- db.beginTransaction();
- try {
- message.updateSendingMessage(conversationId, messageUri, laterTimestamp);
- message.markMessageSent(laterTimestamp);
-
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
-
- BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
- conversationId, message.getMessageId(), laterTimestamp,
- false /* senderBlocked */, false /* shouldAutoSwitchSelfId */);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "InsertNewMessageAction: Inserted broadcast SMS message "
- + message.getMessageId() + ", uri = " + message.getSmsMessageUri());
- }
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- MessagingContentProvider.notifyPartsChanged();
- } else {
- // Ignore error as we only really care about the individual messages?
- LogUtil.e(TAG,
- "InsertNewMessageAction: No uri for broadcast SMS " + message.getMessageId()
- + " inserted into telephony DB");
- }
- }
-
- /**
- * Insert SMS messaging into our database and telephony db.
- */
- private MessageData insertSendingSmsMessage(final MessageData content, final int subId,
- final String recipient, final long timestamp, final String sendingConversationId) {
- sLastSentMessageTimestamp = timestamp;
-
- final Context context = Factory.get().getApplicationContext();
-
- // Inform sync that message is being added at timestamp
- final SyncManager syncManager = DataModel.get().getSyncManager();
- syncManager.onNewMessageInserted(timestamp);
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- // Send a single message
- long threadId;
- String conversationId;
- if (sendingConversationId == null) {
- // For 1:1 message generated sending broadcast need to look up threadId+conversationId
- threadId = MmsUtils.getOrCreateSmsThreadId(context, recipient);
- conversationId = BugleDatabaseOperations.getOrCreateConversationFromRecipient(
- db, threadId, false /* sender blocked */,
- ParticipantData.getFromRawPhoneBySimLocale(recipient, subId));
- } else {
- // Otherwise just look up threadId
- threadId = BugleDatabaseOperations.getThreadId(db, sendingConversationId);
- conversationId = sendingConversationId;
- }
-
- final String messageText = content.getMessageText();
-
- // Insert message into telephony database sms message table
- final Uri messageUri = MmsUtils.insertSmsMessage(context,
- Telephony.Sms.CONTENT_URI,
- subId,
- recipient,
- messageText,
- timestamp,
- Telephony.Sms.STATUS_NONE,
- Telephony.Sms.MESSAGE_TYPE_SENT, threadId);
-
- MessageData message = null;
- if (messageUri != null && !TextUtils.isEmpty(messageUri.toString())) {
- db.beginTransaction();
- try {
- message = MessageData.createDraftSmsMessage(conversationId,
- content.getSelfId(), messageText);
- message.updateSendingMessage(conversationId, messageUri, timestamp);
-
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
-
- // Do not update the conversation summary to reflect autogenerated 1:1 messages
- if (sendingConversationId != null) {
- BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
- conversationId, message.getMessageId(), timestamp,
- false /* senderBlocked */, false /* shouldAutoSwitchSelfId */);
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "InsertNewMessageAction: Inserted SMS message "
- + message.getMessageId() + " (uri = " + message.getSmsMessageUri()
- + ", timestamp = " + message.getReceivedTimeStamp() + ")");
- }
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- MessagingContentProvider.notifyPartsChanged();
- } else {
- LogUtil.e(TAG, "InsertNewMessageAction: No uri for SMS inserted into telephony DB");
- }
-
- return message;
- }
-
- /**
- * Insert MMS messaging into our database.
- */
- private MessageData insertSendingMmsMessage(final String conversationId,
- final MessageData message, final long timestamp) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- final List<MessagePartData> attachmentsUpdated = new ArrayList<>();
- try {
- sLastSentMessageTimestamp = timestamp;
-
- // Insert "draft" message as placeholder until the final message is written to
- // the telephony db
- message.updateSendingMessage(conversationId, null/*messageUri*/, timestamp);
-
- // No need to inform SyncManager as message currently has no Uri...
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
-
- BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
- conversationId, message.getMessageId(), timestamp,
- false /* senderBlocked */, false /* shouldAutoSwitchSelfId */);
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "InsertNewMessageAction: Inserted MMS message "
- + message.getMessageId() + " (timestamp = " + timestamp + ")");
- }
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- MessagingContentProvider.notifyPartsChanged();
-
- return message;
- }
-
- private InsertNewMessageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<InsertNewMessageAction> CREATOR
- = new Parcelable.Creator<InsertNewMessageAction>() {
- @Override
- public InsertNewMessageAction createFromParcel(final Parcel in) {
- return new InsertNewMessageAction(in);
- }
-
- @Override
- public InsertNewMessageAction[] newArray(final int size) {
- return new InsertNewMessageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java b/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java
deleted file mode 100644
index 441a5a2..0000000
--- a/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony.Threads;
-import android.provider.Telephony.ThreadsColumns;
-
-import com.android.messaging.Factory;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.LogUtil;
-
-public class LogTelephonyDatabaseAction extends Action implements Parcelable {
- // Because we use sanitizePII, we should also use BUGLE_TAG
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final String[] ALL_THREADS_PROJECTION = {
- Threads._ID,
- Threads.DATE,
- Threads.MESSAGE_COUNT,
- Threads.RECIPIENT_IDS,
- Threads.SNIPPET,
- Threads.SNIPPET_CHARSET,
- Threads.READ,
- Threads.ERROR,
- Threads.HAS_ATTACHMENT };
-
- // Constants from the Telephony Database
- private static final int ID = 0;
- private static final int DATE = 1;
- private static final int MESSAGE_COUNT = 2;
- private static final int RECIPIENT_IDS = 3;
- private static final int SNIPPET = 4;
- private static final int SNIPPET_CHAR_SET = 5;
- private static final int READ = 6;
- private static final int ERROR = 7;
- private static final int HAS_ATTACHMENT = 8;
-
- /**
- * Log telephony data to logcat
- */
- public static void dumpDatabase() {
- final LogTelephonyDatabaseAction action = new LogTelephonyDatabaseAction();
- action.start();
- }
-
- private LogTelephonyDatabaseAction() {
- }
-
- @Override
- protected Object executeAction() {
- final Context context = Factory.get().getApplicationContext();
-
- if (!DebugUtils.isDebugEnabled()) {
- LogUtil.e(TAG, "Can't log telephony database unless debugging is enabled");
- return null;
- }
-
- if (!LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.w(TAG, "Can't log telephony database unless DEBUG is turned on for TAG: " +
- TAG);
- return null;
- }
-
- LogUtil.d(TAG, "\n");
- LogUtil.d(TAG, "Dump of canoncial_addresses table");
- LogUtil.d(TAG, "*********************************");
-
- Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
- Uri.parse("content://mms-sms/canonical-addresses"), null, null, null, null);
-
- if (cursor == null) {
- LogUtil.w(TAG, "null Cursor in content://mms-sms/canonical-addresses");
- } else {
- try {
- while (cursor.moveToNext()) {
- long id = cursor.getLong(0);
- String number = cursor.getString(1);
- LogUtil.d(TAG, LogUtil.sanitizePII("id: " + id + " number: " + number));
- }
- } finally {
- cursor.close();
- }
- }
-
- LogUtil.d(TAG, "\n");
- LogUtil.d(TAG, "Dump of threads table");
- LogUtil.d(TAG, "*********************");
-
- cursor = SqliteWrapper.query(context, context.getContentResolver(),
- Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(),
- ALL_THREADS_PROJECTION, null, null, "date ASC");
- try {
- while (cursor.moveToNext()) {
- LogUtil.d(TAG, LogUtil.sanitizePII("threadId: " + cursor.getLong(ID) +
- " " + ThreadsColumns.DATE + " : " + cursor.getLong(DATE) +
- " " + ThreadsColumns.MESSAGE_COUNT + " : " + cursor.getInt(MESSAGE_COUNT) +
- " " + ThreadsColumns.SNIPPET + " : " + cursor.getString(SNIPPET) +
- " " + ThreadsColumns.READ + " : " + cursor.getInt(READ) +
- " " + ThreadsColumns.ERROR + " : " + cursor.getInt(ERROR) +
- " " + ThreadsColumns.HAS_ATTACHMENT + " : " +
- cursor.getInt(HAS_ATTACHMENT) +
- " " + ThreadsColumns.RECIPIENT_IDS + " : " +
- cursor.getString(RECIPIENT_IDS)));
- }
- } finally {
- cursor.close();
- }
-
- return null;
- }
-
- private LogTelephonyDatabaseAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<LogTelephonyDatabaseAction> CREATOR
- = new Parcelable.Creator<LogTelephonyDatabaseAction>() {
- @Override
- public LogTelephonyDatabaseAction createFromParcel(final Parcel in) {
- return new LogTelephonyDatabaseAction(in);
- }
-
- @Override
- public LogTelephonyDatabaseAction[] newArray(final int size) {
- return new LogTelephonyDatabaseAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/MarkAsReadAction.java b/src/com/android/messaging/datamodel/action/MarkAsReadAction.java
deleted file mode 100644
index 31bc59d..0000000
--- a/src/com/android/messaging/datamodel/action/MarkAsReadAction.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Action used to mark all the messages in a conversation as read
- */
-public class MarkAsReadAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- private static final String KEY_CONVERSATION_ID = "conversation_id";
-
- /**
- * Mark all the messages as read for a particular conversation.
- */
- public static void markAsRead(final String conversationId) {
- final MarkAsReadAction action = new MarkAsReadAction(conversationId);
- action.start();
- }
-
- private MarkAsReadAction(final String conversationId) {
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- }
-
- @Override
- protected Object executeAction() {
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
-
- // TODO: Consider doing this in background service to avoid delaying other actions
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- // Mark all messages in thread as read in telephony
- final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId);
- if (threadId != -1) {
- MmsUtils.updateSmsReadStatus(threadId, Long.MAX_VALUE);
- }
-
- // Update local db
- db.beginTransaction();
- try {
- final ContentValues values = new ContentValues();
- values.put(MessageColumns.CONVERSATION_ID, conversationId);
- values.put(MessageColumns.READ, 1);
- values.put(MessageColumns.SEEN, 1); // if they read it, they saw it
-
- final int count = db.update(DatabaseHelper.MESSAGES_TABLE, values,
- "(" + MessageColumns.READ + " !=1 OR " +
- MessageColumns.SEEN + " !=1 ) AND " +
- MessageColumns.CONVERSATION_ID + "=?",
- new String[] { conversationId });
- if (count > 0) {
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- // After marking messages as read, update the notifications. This will
- // clear the now stale notifications.
- BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL);
- return null;
- }
-
- private MarkAsReadAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<MarkAsReadAction> CREATOR
- = new Parcelable.Creator<MarkAsReadAction>() {
- @Override
- public MarkAsReadAction createFromParcel(final Parcel in) {
- return new MarkAsReadAction(in);
- }
-
- @Override
- public MarkAsReadAction[] newArray(final int size) {
- return new MarkAsReadAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java b/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java
deleted file mode 100644
index 28f55fd..0000000
--- a/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Action used to mark all messages as seen
- */
-public class MarkAsSeenAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- private static final String KEY_CONVERSATION_ID = "conversation_id";
-
- /**
- * Mark all messages as seen.
- */
- public static void markAllAsSeen() {
- final MarkAsSeenAction action = new MarkAsSeenAction((String) null/*conversationId*/);
- action.start();
- }
-
- /**
- * Mark all messages of a given conversation as seen.
- */
- public static void markAsSeen(final String conversationId) {
- final MarkAsSeenAction action = new MarkAsSeenAction(conversationId);
- action.start();
- }
-
- /**
- * ctor for MarkAsSeenAction.
- * @param conversationId the conversation id for which to mark as seen, or null to mark all
- * messages as seen
- */
- public MarkAsSeenAction(final String conversationId) {
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- }
-
- @Override
- protected Object executeAction() {
- final String conversationId =
- actionParameters.getString(KEY_CONVERSATION_ID);
- final boolean hasSpecificConversation = !TextUtils.isEmpty(conversationId);
-
- // Everything in telephony should already have the seen bit set.
- // Possible exception are messages which did not have seen set and
- // were sync'ed into bugle.
-
- // Now mark the messages as seen in the bugle db
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
-
- try {
- final ContentValues values = new ContentValues();
- values.put(MessageColumns.SEEN, 1);
-
- if (hasSpecificConversation) {
- final int count = db.update(DatabaseHelper.MESSAGES_TABLE, values,
- MessageColumns.SEEN + " != 1 AND " +
- MessageColumns.CONVERSATION_ID + "=?",
- new String[] { conversationId });
- if (count > 0) {
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- }
- } else {
- db.update(DatabaseHelper.MESSAGES_TABLE, values,
- MessageColumns.SEEN + " != 1", null/*selectionArgs*/);
- }
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- // After marking messages as seen, update the notifications. This will
- // clear the now stale notifications.
- BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL);
- return null;
- }
-
- private MarkAsSeenAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<MarkAsSeenAction> CREATOR
- = new Parcelable.Creator<MarkAsSeenAction>() {
- @Override
- public MarkAsSeenAction createFromParcel(final Parcel in) {
- return new MarkAsSeenAction(in);
- }
-
- @Override
- public MarkAsSeenAction[] newArray(final int size) {
- return new MarkAsSeenAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java b/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java
deleted file mode 100644
index fbd4e82..0000000
--- a/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.concurrent.TimeUnit;
-
-public class ProcessDeliveryReportAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- private static final String KEY_URI = "uri";
- private static final String KEY_STATUS = "status";
-
- private ProcessDeliveryReportAction(final Uri uri, final int status) {
- actionParameters.putParcelable(KEY_URI, uri);
- actionParameters.putInt(KEY_STATUS, status);
- }
-
- public static void deliveryReportReceived(final Uri uri, final int status) {
- final ProcessDeliveryReportAction action = new ProcessDeliveryReportAction(uri, status);
- action.start();
- }
-
- @Override
- protected Object executeAction() {
- final Uri smsMessageUri = actionParameters.getParcelable(KEY_URI);
- final int status = actionParameters.getInt(KEY_STATUS);
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- final long messageRowId = ContentUris.parseId(smsMessageUri);
- if (messageRowId < 0) {
- LogUtil.e(TAG, "ProcessDeliveryReportAction: can't find message");
- return null;
- }
- final long timeSentInMillis = System.currentTimeMillis();
- // Update telephony provider
- if (smsMessageUri != null) {
- MmsUtils.updateSmsStatusAndDateSent(smsMessageUri, status, timeSentInMillis);
- }
-
- // Update local message
- db.beginTransaction();
- try {
- final ContentValues values = new ContentValues();
- final int bugleStatus = SyncMessageBatch.bugleStatusForSms(true /*outgoing*/,
- Telephony.Sms.MESSAGE_TYPE_SENT /* type */, status);
- values.put(DatabaseHelper.MessageColumns.STATUS, bugleStatus);
- values.put(DatabaseHelper.MessageColumns.SENT_TIMESTAMP,
- TimeUnit.MILLISECONDS.toMicros(timeSentInMillis));
-
- final MessageData messageData =
- BugleDatabaseOperations.readMessageData(db, smsMessageUri);
-
- // Check the message was not removed before the delivery report comes in
- if (messageData != null) {
- Assert.isTrue(smsMessageUri.equals(messageData.getSmsMessageUri()));
-
- // Row must exist as was just loaded above (on ActionService thread)
- BugleDatabaseOperations.updateMessageRow(db, messageData.getMessageId(), values);
-
- MessagingContentProvider.notifyMessagesChanged(messageData.getConversationId());
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- return null;
- }
-
- private ProcessDeliveryReportAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<ProcessDeliveryReportAction> CREATOR
- = new Parcelable.Creator<ProcessDeliveryReportAction>() {
- @Override
- public ProcessDeliveryReportAction createFromParcel(final Parcel in) {
- return new ProcessDeliveryReportAction(in);
- }
-
- @Override
- public ProcessDeliveryReportAction[] newArray(final int size) {
- return new ProcessDeliveryReportAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java b/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java
deleted file mode 100644
index 757ea05..0000000
--- a/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java
+++ /dev/null
@@ -1,573 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.app.Activity;
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony.Mms;
-import android.telephony.SmsManager;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DataModelException;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.MmsFileProvider;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.mmslib.pdu.RetrieveConf;
-import com.android.messaging.sms.DatabaseMessages;
-import com.android.messaging.sms.MmsSender;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Processes an MMS message after it has been downloaded.
- * NOTE: This action must queue a ProcessPendingMessagesAction when it is done (success or failure).
- */
-public class ProcessDownloadedMmsAction extends Action {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- // Always set when message downloaded
- private static final String KEY_DOWNLOADED_BY_PLATFORM = "downloaded_by_platform";
- private static final String KEY_MESSAGE_ID = "message_id";
- private static final String KEY_NOTIFICATION_URI = "notification_uri";
- private static final String KEY_CONVERSATION_ID = "conversation_id";
- private static final String KEY_PARTICIPANT_ID = "participant_id";
- private static final String KEY_STATUS_IF_FAILED = "status_if_failed";
-
- // Set when message downloaded by platform (L+)
- private static final String KEY_RESULT_CODE = "result_code";
- private static final String KEY_HTTP_STATUS_CODE = "http_status_code";
- private static final String KEY_CONTENT_URI = "content_uri";
- private static final String KEY_SUB_ID = "sub_id";
- private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
- private static final String KEY_TRANSACTION_ID = "transaction_id";
- private static final String KEY_CONTENT_LOCATION = "content_location";
- private static final String KEY_AUTO_DOWNLOAD = "auto_download";
- private static final String KEY_RECEIVED_TIMESTAMP = "received_timestamp";
-
- // Set when message downloaded by us (legacy)
- private static final String KEY_STATUS = "status";
- private static final String KEY_RAW_STATUS = "raw_status";
- private static final String KEY_MMS_URI = "mms_uri";
-
- // Used to send a deferred response in response to auto-download failure
- private static final String KEY_SEND_DEFERRED_RESP_STATUS = "send_deferred_resp_status";
-
- // Results passed from background worker to processCompletion
- private static final String BUNDLE_REQUEST_STATUS = "request_status";
- private static final String BUNDLE_RAW_TELEPHONY_STATUS = "raw_status";
- private static final String BUNDLE_MMS_URI = "mms_uri";
-
- // This is called when MMS lib API returns via PendingIntent
- public static void processMessageDownloaded(final int resultCode, final Bundle extras) {
- final String messageId = extras.getString(DownloadMmsAction.EXTRA_MESSAGE_ID);
- final Uri contentUri = extras.getParcelable(DownloadMmsAction.EXTRA_CONTENT_URI);
- final Uri notificationUri = extras.getParcelable(DownloadMmsAction.EXTRA_NOTIFICATION_URI);
- final String conversationId = extras.getString(DownloadMmsAction.EXTRA_CONVERSATION_ID);
- final String participantId = extras.getString(DownloadMmsAction.EXTRA_PARTICIPANT_ID);
- Assert.notNull(messageId);
- Assert.notNull(contentUri);
- Assert.notNull(notificationUri);
- Assert.notNull(conversationId);
- Assert.notNull(participantId);
-
- final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
- final Bundle params = action.actionParameters;
- params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, true);
- params.putString(KEY_MESSAGE_ID, messageId);
- params.putInt(KEY_RESULT_CODE, resultCode);
- params.putInt(KEY_HTTP_STATUS_CODE,
- extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0));
- params.putParcelable(KEY_CONTENT_URI, contentUri);
- params.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
- params.putInt(KEY_SUB_ID,
- extras.getInt(DownloadMmsAction.EXTRA_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
- params.putString(KEY_SUB_PHONE_NUMBER,
- extras.getString(DownloadMmsAction.EXTRA_SUB_PHONE_NUMBER));
- params.putString(KEY_TRANSACTION_ID,
- extras.getString(DownloadMmsAction.EXTRA_TRANSACTION_ID));
- params.putString(KEY_CONTENT_LOCATION,
- extras.getString(DownloadMmsAction.EXTRA_CONTENT_LOCATION));
- params.putBoolean(KEY_AUTO_DOWNLOAD,
- extras.getBoolean(DownloadMmsAction.EXTRA_AUTO_DOWNLOAD));
- params.putLong(KEY_RECEIVED_TIMESTAMP,
- extras.getLong(DownloadMmsAction.EXTRA_RECEIVED_TIMESTAMP));
- params.putString(KEY_CONVERSATION_ID, conversationId);
- params.putString(KEY_PARTICIPANT_ID, participantId);
- params.putInt(KEY_STATUS_IF_FAILED,
- extras.getInt(DownloadMmsAction.EXTRA_STATUS_IF_FAILED));
- action.start();
- }
-
- // This is called for fast failing downloading (due to airplane mode or mobile data )
- public static void processMessageDownloadFastFailed(final String messageId,
- final Uri notificationUri, final String conversationId, final String participantId,
- final String contentLocation, final int subId, final String subPhoneNumber,
- final int statusIfFailed, final boolean autoDownload, final String transactionId,
- final int resultCode) {
- Assert.notNull(messageId);
- Assert.notNull(notificationUri);
- Assert.notNull(conversationId);
- Assert.notNull(participantId);
-
- final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
- final Bundle params = action.actionParameters;
- params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, true);
- params.putString(KEY_MESSAGE_ID, messageId);
- params.putInt(KEY_RESULT_CODE, resultCode);
- params.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
- params.putInt(KEY_SUB_ID, subId);
- params.putString(KEY_SUB_PHONE_NUMBER, subPhoneNumber);
- params.putString(KEY_CONTENT_LOCATION, contentLocation);
- params.putBoolean(KEY_AUTO_DOWNLOAD, autoDownload);
- params.putString(KEY_CONVERSATION_ID, conversationId);
- params.putString(KEY_PARTICIPANT_ID, participantId);
- params.putInt(KEY_STATUS_IF_FAILED, statusIfFailed);
- params.putString(KEY_TRANSACTION_ID, transactionId);
- action.start();
- }
-
- public static void processDownloadActionFailure(final String messageId, final int status,
- final int rawStatus, final String conversationId, final String participantId,
- final int statusIfFailed, final int subId, final String transactionId) {
- Assert.notNull(messageId);
- Assert.notNull(conversationId);
- Assert.notNull(participantId);
-
- final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
- final Bundle params = action.actionParameters;
- params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, false);
- params.putString(KEY_MESSAGE_ID, messageId);
- params.putInt(KEY_STATUS, status);
- params.putInt(KEY_RAW_STATUS, rawStatus);
- params.putString(KEY_CONVERSATION_ID, conversationId);
- params.putString(KEY_PARTICIPANT_ID, participantId);
- params.putInt(KEY_STATUS_IF_FAILED, statusIfFailed);
- params.putInt(KEY_SUB_ID, subId);
- params.putString(KEY_TRANSACTION_ID, transactionId);
- action.start();
- }
-
- public static void sendDeferredRespStatus(final String messageId, final String transactionId,
- final String contentLocation, final int subId) {
- final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
- final Bundle params = action.actionParameters;
- params.putString(KEY_MESSAGE_ID, messageId);
- params.putString(KEY_TRANSACTION_ID, transactionId);
- params.putString(KEY_CONTENT_LOCATION, contentLocation);
- params.putBoolean(KEY_SEND_DEFERRED_RESP_STATUS, true);
- params.putInt(KEY_SUB_ID, subId);
- action.start();
- }
-
- private ProcessDownloadedMmsAction() {
- // Callers must use one of the static methods above
- }
-
- @Override
- protected Object executeAction() {
- // Fire up the background worker
- requestBackgroundWork();
- return null;
- }
-
- @Override
- protected Bundle doBackgroundWork() throws DataModelException {
- final Context context = Factory.get().getApplicationContext();
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
- final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
- final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
- final boolean sendDeferredRespStatus =
- actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS, false);
-
- // Send a response indicating that auto-download failed
- if (sendDeferredRespStatus) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "DownloadMmsAction: Auto-download of message " + messageId
- + " failed; sending DEFERRED NotifyRespInd");
- }
- MmsUtils.sendNotifyResponseForMmsDownload(
- context,
- subId,
- MmsUtils.stringToBytes(transactionId, "UTF-8"),
- contentLocation,
- PduHeaders.STATUS_DEFERRED);
- return null;
- }
-
- // Processing a real MMS download
- final boolean downloadedByPlatform = actionParameters.getBoolean(
- KEY_DOWNLOADED_BY_PLATFORM);
-
- final int status;
- int rawStatus = MmsUtils.PDU_HEADER_VALUE_UNDEFINED;
- Uri mmsUri = null;
-
- if (downloadedByPlatform) {
- final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
- if (resultCode == Activity.RESULT_OK) {
- final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI);
- final File downloadedFile = MmsFileProvider.getFile(contentUri);
- byte[] downloadedData = null;
- try {
- downloadedData = Files.toByteArray(downloadedFile);
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "ProcessDownloadedMmsAction: MMS download file not found: "
- + downloadedFile.getAbsolutePath());
- } catch (final IOException e) {
- LogUtil.e(TAG, "ProcessDownloadedMmsAction: Error reading MMS download file: "
- + downloadedFile.getAbsolutePath(), e);
- }
-
- // Can delete the temp file now
- if (downloadedFile.exists()) {
- downloadedFile.delete();
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "ProcessDownloadedMmsAction: Deleted temp file with "
- + "downloaded MMS pdu: " + downloadedFile.getAbsolutePath());
- }
- }
-
- if (downloadedData != null) {
- final RetrieveConf retrieveConf =
- MmsSender.parseRetrieveConf(downloadedData, subId);
- if (MmsUtils.isDumpMmsEnabled()) {
- MmsUtils.dumpPdu(downloadedData, retrieveConf);
- }
- if (retrieveConf != null) {
- // Insert the downloaded MMS into telephony
- final Uri notificationUri = actionParameters.getParcelable(
- KEY_NOTIFICATION_URI);
- final String subPhoneNumber = actionParameters.getString(
- KEY_SUB_PHONE_NUMBER);
- final boolean autoDownload = actionParameters.getBoolean(
- KEY_AUTO_DOWNLOAD);
- final long receivedTimestampInSeconds =
- actionParameters.getLong(KEY_RECEIVED_TIMESTAMP);
-
- // Inform sync we're adding a message to telephony
- final SyncManager syncManager = DataModel.get().getSyncManager();
- syncManager.onNewMessageInserted(receivedTimestampInSeconds * 1000L);
-
- final MmsUtils.StatusPlusUri result =
- MmsUtils.insertDownloadedMessageAndSendResponse(context,
- notificationUri, subId, subPhoneNumber, transactionId,
- contentLocation, autoDownload, receivedTimestampInSeconds,
- retrieveConf);
- status = result.status;
- rawStatus = result.rawStatus;
- mmsUri = result.uri;
- } else {
- // Invalid response PDU
- status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
- }
- } else {
- // Failed to read download file
- status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
- }
- } else {
- LogUtil.w(TAG, "ProcessDownloadedMmsAction: Platform returned error resultCode: "
- + resultCode);
- final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
- status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode);
- }
- } else {
- // Message was already processed by the internal API, or the download action failed.
- // In either case, we just need to copy the status to the response bundle.
- status = actionParameters.getInt(KEY_STATUS);
- rawStatus = actionParameters.getInt(KEY_RAW_STATUS);
- mmsUri = actionParameters.getParcelable(KEY_MMS_URI);
- }
-
- final Bundle response = new Bundle();
- response.putInt(BUNDLE_REQUEST_STATUS, status);
- response.putInt(BUNDLE_RAW_TELEPHONY_STATUS, rawStatus);
- response.putParcelable(BUNDLE_MMS_URI, mmsUri);
- return response;
- }
-
- @Override
- protected Object processBackgroundResponse(final Bundle response) {
- if (response == null) {
- // No message download to process; doBackgroundWork sent a notify deferred response
- Assert.isTrue(actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS));
- return null;
- }
-
- final int status = response.getInt(BUNDLE_REQUEST_STATUS);
- final int rawStatus = response.getInt(BUNDLE_RAW_TELEPHONY_STATUS);
- final Uri messageUri = response.getParcelable(BUNDLE_MMS_URI);
- final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD);
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
-
- // Do post-processing on downloaded message
- final MessageData message = processResult(status, rawStatus, messageUri);
-
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
- // If we were trying to auto-download but have failed need to send the deferred response
- if (autoDownload && message == null && status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) {
- final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
- final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
- sendDeferredRespStatus(messageId, transactionId, contentLocation, subId);
- }
-
- if (autoDownload) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- MessageData toastMessage = message;
- if (toastMessage == null) {
- // If the downloaded failed (message is null), then we should announce the
- // receiving of the wap push message. Load the wap push message here instead.
- toastMessage = BugleDatabaseOperations.readMessageData(db, messageId);
- }
- if (toastMessage != null) {
- final ParticipantData sender = ParticipantData.getFromId(
- db, toastMessage.getParticipantId());
- BugleActionToasts.onMessageReceived(
- toastMessage.getConversationId(), sender, toastMessage);
- }
- } else {
- final boolean success = message != null && status == MmsUtils.MMS_REQUEST_SUCCEEDED;
- BugleActionToasts.onSendMessageOrManualDownloadActionCompleted(
- // If download failed, use the wap push message's conversation instead
- success ? message.getConversationId()
- : actionParameters.getString(KEY_CONVERSATION_ID),
- success, status, false/*isSms*/, subId, false /*isSend*/);
- }
-
- final boolean failed = (messageUri == null);
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(failed, this);
- if (failed) {
- BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS);
- }
-
- return message;
- }
-
- @Override
- protected Object processBackgroundFailure() {
- if (actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS)) {
- // We can early-out for these failures. processResult is only designed to handle
- // post-processing of MMS downloads (whether successful or not).
- LogUtil.w(TAG,
- "ProcessDownloadedMmsAction: Exception while sending deferred NotifyRespInd");
- return null;
- }
-
- // Background worker threw an exception; require manual retry
- processResult(MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
- null /* mmsUri */);
-
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(true /* failed */,
- this);
-
- return null;
- }
-
- private MessageData processResult(final int status, final int rawStatus, final Uri mmsUri) {
- final Context context = Factory.get().getApplicationContext();
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
- final Uri mmsNotificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI);
- final String notificationConversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- final String notificationParticipantId = actionParameters.getString(KEY_PARTICIPANT_ID);
- final int statusIfFailed = actionParameters.getInt(KEY_STATUS_IF_FAILED);
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
-
- Assert.notNull(messageId);
-
- LogUtil.i(TAG, "ProcessDownloadedMmsAction: Processed MMS download of message " + messageId
- + "; status is " + MmsUtils.getRequestStatusDescription(status));
-
- DatabaseMessages.MmsMessage mms = null;
- if (status == MmsUtils.MMS_REQUEST_SUCCEEDED && mmsUri != null) {
- // Delete the initial M-Notification.ind from telephony
- SqliteWrapper.delete(context, context.getContentResolver(),
- mmsNotificationUri, null, null);
-
- // Read the sent MMS from the telephony provider
- mms = MmsUtils.loadMms(mmsUri);
- }
-
- boolean messageInFocusedConversation = false;
- boolean messageInObservableConversation = false;
- String conversationId = null;
- MessageData message = null;
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- try {
- if (mms != null) {
- final ParticipantData self = ParticipantData.getSelfParticipant(mms.getSubId());
- final String selfId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
-
- final List<String> recipients = MmsUtils.getRecipientsByThread(mms.mThreadId);
- String from = MmsUtils.getMmsSender(recipients, mms.getUri());
- if (from == null) {
- LogUtil.w(TAG,
- "Downloaded an MMS without sender address; using unknown sender.");
- from = ParticipantData.getUnknownSenderDestination();
- }
- final ParticipantData sender = ParticipantData.getFromRawPhoneBySimLocale(from,
- subId);
- final String senderParticipantId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender);
- if (!senderParticipantId.equals(notificationParticipantId)) {
- LogUtil.e(TAG, "ProcessDownloadedMmsAction: Downloaded MMS message "
- + messageId + " has different sender (participantId = "
- + senderParticipantId + ") than notification ("
- + notificationParticipantId + ")");
- }
- final boolean blockedSender = BugleDatabaseOperations.isBlockedDestination(
- db, sender.getNormalizedDestination());
- conversationId = BugleDatabaseOperations.getOrCreateConversationFromThreadId(db,
- mms.mThreadId, blockedSender, subId);
-
- messageInFocusedConversation =
- DataModel.get().isFocusedConversation(conversationId);
- messageInObservableConversation =
- DataModel.get().isNewMessageObservable(conversationId);
-
- // TODO: Also write these values to the telephony provider
- mms.mRead = messageInFocusedConversation;
- mms.mSeen = messageInObservableConversation;
-
- // Translate to our format
- message = MmsUtils.createMmsMessage(mms, conversationId, senderParticipantId,
- selfId, MessageData.BUGLE_STATUS_INCOMING_COMPLETE);
- // Update image sizes.
- message.updateSizesForImageParts();
- // Inform sync that message has been added at local received timestamp
- final SyncManager syncManager = DataModel.get().getSyncManager();
- syncManager.onNewMessageInserted(message.getReceivedTimeStamp());
- final MessageData current = BugleDatabaseOperations.readMessageData(db, messageId);
- if (current == null) {
- LogUtil.w(TAG, "Message deleted prior to update");
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
- } else {
- // Overwrite existing notification message
- message.updateMessageId(messageId);
- // Write message
- BugleDatabaseOperations.updateMessageInTransaction(db, message);
- }
-
- if (!TextUtils.equals(notificationConversationId, conversationId)) {
- // If this is a group conversation, the message is moved. So the original
- // 1v1 conversation (as referenced by notificationConversationId) could
- // be left with no non-draft message. Delete the conversation if that
- // happens. See the comment for the method below for why we need to do this.
- if (!BugleDatabaseOperations.deleteConversationIfEmptyInTransaction(
- db, notificationConversationId)) {
- BugleDatabaseOperations.maybeRefreshConversationMetadataInTransaction(
- db, notificationConversationId, messageId,
- true /*shouldAutoSwitchSelfId*/, blockedSender /*keepArchived*/);
- }
- }
-
- BugleDatabaseOperations.refreshConversationMetadataInTransaction(db, conversationId,
- true /*shouldAutoSwitchSelfId*/, blockedSender /*keepArchived*/);
- } else {
- messageInFocusedConversation =
- DataModel.get().isFocusedConversation(notificationConversationId);
-
- // Default to retry status unless status indicates otherwise
- int bugleStatus = statusIfFailed;
- if (status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) {
- bugleStatus = MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED;
- } else if (status == MmsUtils.MMS_REQUEST_NO_RETRY) {
- bugleStatus = MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE;
- }
- DownloadMmsAction.updateMessageStatus(mmsNotificationUri, messageId,
- notificationConversationId, bugleStatus, rawStatus);
-
- // Log MMS download failed
- final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
- final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
-
- // Just in case this was the latest message update the summary data
- BugleDatabaseOperations.refreshConversationMetadataInTransaction(db,
- notificationConversationId, true /*shouldAutoSwitchSelfId*/,
- false /*keepArchived*/);
- }
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- if (mmsUri != null) {
- // Update mms table with read status now we know the conversation id
- final ContentValues values = new ContentValues(1);
- values.put(Mms.READ, messageInFocusedConversation);
- SqliteWrapper.update(context, context.getContentResolver(), mmsUri, values,
- null, null);
- }
-
- // Show a notification to let the user know a new message has arrived
- BugleNotifications.update(false /*silent*/, conversationId, BugleNotifications.UPDATE_ALL);
-
- // Messages may have changed in two conversations
- if (conversationId != null) {
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- }
- MessagingContentProvider.notifyMessagesChanged(notificationConversationId);
- MessagingContentProvider.notifyPartsChanged();
-
- return message;
- }
-
- private ProcessDownloadedMmsAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<ProcessDownloadedMmsAction> CREATOR
- = new Parcelable.Creator<ProcessDownloadedMmsAction>() {
- @Override
- public ProcessDownloadedMmsAction createFromParcel(final Parcel in) {
- return new ProcessDownloadedMmsAction(in);
- }
-
- @Override
- public ProcessDownloadedMmsAction[] newArray(final int size) {
- return new ProcessDownloadedMmsAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java b/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
deleted file mode 100644
index 8a41f4a..0000000
--- a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.ConnectivityManager;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.telephony.ServiceState;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.BuglePrefsKeys;
-import com.android.messaging.util.ConnectivityUtil.ConnectivityListener;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Action used to lookup any messages in the pending send/download state and either fail them or
- * retry their action. This action only initiates one retry at a time - further retries should be
- * triggered by successful sending of a message, network status change or exponential backoff timer.
- */
-public class ProcessPendingMessagesAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- private static final int PENDING_INTENT_REQUEST_CODE = 101;
-
- public static void processFirstPendingMessage() {
- // Clear any pending alarms or connectivity events
- unregister();
- // Clear retry count
- setRetry(0);
-
- // Start action
- final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
- action.start();
- }
-
- public static void scheduleProcessPendingMessagesAction(final boolean failed,
- final Action processingAction) {
- LogUtil.i(TAG, "ProcessPendingMessagesAction: Scheduling pending messages"
- + (failed ? "(message failed)" : ""));
- // Can safely clear any pending alarms or connectivity events as either an action
- // is currently running or we will run now or register if pending actions possible.
- unregister();
-
- final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
- boolean scheduleAlarm = false;
- // If message succeeded and if Bugle is default SMS app just carry on with next message
- if (!failed && isDefaultSmsApp) {
- // Clear retry attempt count as something just succeeded
- setRetry(0);
-
- // Lookup and queue next message for immediate processing by background worker
- // iff there are no pending messages this will do nothing and return true.
- final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
- if (action.queueActions(processingAction)) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- if (processingAction.hasBackgroundActions()) {
- LogUtil.v(TAG, "ProcessPendingMessagesAction: Action queued");
- } else {
- LogUtil.v(TAG, "ProcessPendingMessagesAction: No actions to queue");
- }
- }
- // Have queued next action if needed, nothing more to do
- return;
- }
- // In case of error queuing schedule a retry
- scheduleAlarm = true;
- LogUtil.w(TAG, "ProcessPendingMessagesAction: Action failed to queue; retrying");
- }
- if (getHavePendingMessages() || scheduleAlarm) {
- // Still have a pending message that needs to be queued for processing
- final ConnectivityListener listener = new ConnectivityListener() {
- @Override
- public void onConnectivityStateChanged(final Context context, final Intent intent) {
- final int networkType =
- MmsUtils.getConnectivityEventNetworkType(context, intent);
- if (networkType != ConnectivityManager.TYPE_MOBILE) {
- return;
- }
- final boolean isConnected = !intent.getBooleanExtra(
- ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
- // TODO: Should we check in more detail?
- if (isConnected) {
- onConnected();
- }
- }
-
- @Override
- public void onPhoneStateChanged(final Context context, final int serviceState) {
- if (serviceState == ServiceState.STATE_IN_SERVICE) {
- onConnected();
- }
- }
-
- private void onConnected() {
- LogUtil.i(TAG, "ProcessPendingMessagesAction: Now connected; starting action");
-
- // Clear any pending alarms or connectivity events but leave attempt count alone
- unregister();
-
- // Start action
- final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
- action.start();
- }
- };
- // Read and increment attempt number from shared prefs
- final int retryAttempt = getNextRetry();
- register(listener, retryAttempt);
- } else {
- // No more pending messages (presumably the message that failed has expired) or it
- // may be possible that a send and a download are already in process.
- // Clear retry attempt count.
- // TODO Might be premature if send and download in process...
- // but worst case means we try to send a bit more often.
- setRetry(0);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "ProcessPendingMessagesAction: No more pending messages");
- }
- }
- }
-
- private static void register(final ConnectivityListener listener, final int retryAttempt) {
- int retryNumber = retryAttempt;
-
- // Register to be notified about connectivity changes
- DataModel.get().getConnectivityUtil().register(listener);
-
- final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
- final long initialBackoffMs = BugleGservices.get().getLong(
- BugleGservicesKeys.INITIAL_MESSAGE_RESEND_DELAY_MS,
- BugleGservicesKeys.INITIAL_MESSAGE_RESEND_DELAY_MS_DEFAULT);
- final long maxDelayMs = BugleGservices.get().getLong(
- BugleGservicesKeys.MAX_MESSAGE_RESEND_DELAY_MS,
- BugleGservicesKeys.MAX_MESSAGE_RESEND_DELAY_MS_DEFAULT);
- long delayMs;
- long nextDelayMs = initialBackoffMs;
- do {
- delayMs = nextDelayMs;
- retryNumber--;
- nextDelayMs = delayMs * 2;
- }
- while (retryNumber > 0 && nextDelayMs < maxDelayMs);
-
- LogUtil.i(TAG, "ProcessPendingMessagesAction: Registering for retry #" + retryAttempt
- + " in " + delayMs + " ms");
-
- action.schedule(PENDING_INTENT_REQUEST_CODE, delayMs);
- }
-
- private static void unregister() {
- // Clear any pending alarms or connectivity events
- DataModel.get().getConnectivityUtil().unregister();
-
- final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
- action.schedule(PENDING_INTENT_REQUEST_CODE, Long.MAX_VALUE);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "ProcessPendingMessagesAction: Unregistering for connectivity changed "
- + "events and clearing scheduled alarm");
- }
- }
-
- private static void setRetry(final int retryAttempt) {
- final BuglePrefs prefs = Factory.get().getApplicationPrefs();
- prefs.putInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, retryAttempt);
- }
-
- private static int getNextRetry() {
- final BuglePrefs prefs = Factory.get().getApplicationPrefs();
- final int retryAttempt =
- prefs.getInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, 0) + 1;
- prefs.putInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, retryAttempt);
- return retryAttempt;
- }
-
- private ProcessPendingMessagesAction() {
- }
-
- /**
- * Read from the DB and determine if there are any messages we should process
- * @return true if we have pending messages
- */
- private static boolean getHavePendingMessages() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final long now = System.currentTimeMillis();
-
- final String toSendMessageId = findNextMessageToSend(db, now);
- if (toSendMessageId != null) {
- return true;
- } else {
- final String toDownloadMessageId = findNextMessageToDownload(db, now);
- if (toDownloadMessageId != null) {
- return true;
- }
- }
- // Messages may be in the process of sending/downloading even when there are no pending
- // messages...
- return false;
- }
-
- /**
- * Queue any pending actions
- * @param actionState
- * @return true if action queued (or no actions to queue) else false
- */
- private boolean queueActions(final Action processingAction) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final long now = System.currentTimeMillis();
- boolean succeeded = true;
-
- // Will queue no more than one message to send plus one message to download
- // This keeps outgoing messages "in order" but allow downloads to happen even if sending
- // gets blocked until messages time out. Manual resend bumps messages to head of queue.
- final String toSendMessageId = findNextMessageToSend(db, now);
- final String toDownloadMessageId = findNextMessageToDownload(db, now);
- if (toSendMessageId != null) {
- LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toSendMessageId
- + " for sending");
- // This could queue nothing
- if (!SendMessageAction.queueForSendInBackground(toSendMessageId, processingAction)) {
- LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
- + toSendMessageId + " for sending");
- succeeded = false;
- }
- }
- if (toDownloadMessageId != null) {
- LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toDownloadMessageId
- + " for download");
- // This could queue nothing
- if (!DownloadMmsAction.queueMmsForDownloadInBackground(toDownloadMessageId,
- processingAction)) {
- LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
- + toDownloadMessageId + " for download");
- succeeded = false;
- }
- }
- if (toSendMessageId == null && toDownloadMessageId == null) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "ProcessPendingMessagesAction: No messages to send or download");
- }
- }
- return succeeded;
- }
-
- @Override
- protected Object executeAction() {
- // If triggered by alarm will not have unregistered yet
- unregister();
-
- if (PhoneUtils.getDefault().isDefaultSmsApp()) {
- queueActions(this);
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "ProcessPendingMessagesAction: Not default SMS app; rescheduling");
- }
- scheduleProcessPendingMessagesAction(true, this);
- }
-
- return null;
- }
-
- private static String findNextMessageToSend(final DatabaseWrapper db, final long now) {
- String toSendMessageId = null;
- db.beginTransaction();
- Cursor sending = null;
- Cursor cursor = null;
- int sendingCnt = 0;
- int pendingCnt = 0;
- int failedCnt = 0;
- try {
- // First check to see if we have any messages already sending
- sending = db.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(),
- DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
- new String[]{Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_SENDING),
- Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING)},
- null,
- null,
- DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
- final boolean messageCurrentlySending = sending.moveToNext();
- sendingCnt = sending.getCount();
- // Look for messages we could send
- final ContentValues values = new ContentValues();
- values.put(DatabaseHelper.MessageColumns.STATUS,
- MessageData.BUGLE_STATUS_OUTGOING_FAILED);
- cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(),
- DatabaseHelper.MessageColumns.STATUS + " IN ("
- + MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ","
- + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ")",
- null,
- null,
- null,
- DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
- pendingCnt = cursor.getCount();
-
- while (cursor.moveToNext()) {
- final MessageData message = new MessageData();
- message.bind(cursor);
- if (message.getInResendWindow(now)) {
- // If no messages currently sending
- if (!messageCurrentlySending) {
- // Resend this message
- toSendMessageId = message.getMessageId();
- // Before queuing the message for resending, check if the message's self is
- // active. If not, switch back to the system's default subscription.
- if (OsUtil.isAtLeastL_MR1()) {
- final ParticipantData messageSelf = BugleDatabaseOperations
- .getExistingParticipant(db, message.getSelfId());
- if (messageSelf == null || !messageSelf.isActiveSubscription()) {
- final ParticipantData defaultSelf = BugleDatabaseOperations
- .getOrCreateSelf(db, PhoneUtils.getDefault()
- .getDefaultSmsSubscriptionId());
- if (defaultSelf != null) {
- message.bindSelfId(defaultSelf.getId());
- final ContentValues selfValues = new ContentValues();
- selfValues.put(MessageColumns.SELF_PARTICIPANT_ID,
- defaultSelf.getId());
- BugleDatabaseOperations.updateMessageRow(db,
- message.getMessageId(), selfValues);
- MessagingContentProvider.notifyMessagesChanged(
- message.getConversationId());
- }
- }
- }
- }
- break;
- } else {
- failedCnt++;
-
- // Mark message as failed
- BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values);
- MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
- }
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- if (cursor != null) {
- cursor.close();
- }
- if (sending != null) {
- sending.close();
- }
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "ProcessPendingMessagesAction: "
- + sendingCnt + " messages already sending, "
- + pendingCnt + " messages to send, "
- + failedCnt + " failed messages");
- }
-
- return toSendMessageId;
- }
-
- private static String findNextMessageToDownload(final DatabaseWrapper db, final long now) {
- String toDownloadMessageId = null;
- db.beginTransaction();
- Cursor cursor = null;
- int downloadingCnt = 0;
- int pendingCnt = 0;
- try {
- // First check if we have any messages already downloading
- downloadingCnt = (int) db.queryNumEntries(DatabaseHelper.MESSAGES_TABLE,
- DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
- new String[] {
- Integer.toString(MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING),
- Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING)
- });
-
- // TODO: This query is not actually needed if downloadingCnt == 0.
- cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(),
- DatabaseHelper.MessageColumns.STATUS + " =? OR "
- + DatabaseHelper.MessageColumns.STATUS + " =?",
- new String[]{
- Integer.toString(
- MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD),
- Integer.toString(
- MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD)
- },
- null,
- null,
- DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
-
- pendingCnt = cursor.getCount();
-
- // If no messages are currently downloading and there is a download pending,
- // queue the download of the oldest pending message.
- if (downloadingCnt == 0 && cursor.moveToNext()) {
- // Always start the next pending message. We will check if a download has
- // expired in DownloadMmsAction and mark message failed there.
- final MessageData message = new MessageData();
- message.bind(cursor);
- toDownloadMessageId = message.getMessageId();
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- if (cursor != null) {
- cursor.close();
- }
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "ProcessPendingMessagesAction: "
- + downloadingCnt + " messages already downloading, "
- + pendingCnt + " messages to download");
- }
-
- return toDownloadMessageId;
- }
-
- private ProcessPendingMessagesAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<ProcessPendingMessagesAction> CREATOR
- = new Parcelable.Creator<ProcessPendingMessagesAction>() {
- @Override
- public ProcessPendingMessagesAction createFromParcel(final Parcel in) {
- return new ProcessPendingMessagesAction(in);
- }
-
- @Override
- public ProcessPendingMessagesAction[] newArray(final int size) {
- return new ProcessPendingMessagesAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java b/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java
deleted file mode 100644
index f408e47..0000000
--- a/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.app.Activity;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SmsManager;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MmsFileProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.pdu.SendConf;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.sms.MmsSender;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.io.File;
-import java.util.ArrayList;
-
-/**
-* Update message status to reflect success or failure
-* Can also update the message itself if a "final" message is now available from telephony db
-*/
-public class ProcessSentMessageAction extends Action {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- // These are always set
- private static final String KEY_SMS = "is_sms";
- private static final String KEY_SENT_BY_PLATFORM = "sent_by_platform";
-
- // These are set when we're processing a message sent by the user. They are null for messages
- // sent automatically (e.g. a NotifyRespInd/AcknowledgeInd sent in response to a download).
- private static final String KEY_MESSAGE_ID = "message_id";
- private static final String KEY_MESSAGE_URI = "message_uri";
- private static final String KEY_UPDATED_MESSAGE_URI = "updated_message_uri";
- private static final String KEY_SUB_ID = "sub_id";
-
- // These are set for messages sent by the platform (L+)
- public static final String KEY_RESULT_CODE = "result_code";
- public static final String KEY_HTTP_STATUS_CODE = "http_status_code";
- private static final String KEY_CONTENT_URI = "content_uri";
- private static final String KEY_RESPONSE = "response";
- private static final String KEY_RESPONSE_IMPORTANT = "response_important";
-
- // These are set for messages we sent ourself (legacy), or which we fast-failed before sending.
- private static final String KEY_STATUS = "status";
- private static final String KEY_RAW_STATUS = "raw_status";
-
- // This is called when MMS lib API returns via PendingIntent
- public static void processMmsSent(final int resultCode, final Uri messageUri,
- final Bundle extras) {
- final ProcessSentMessageAction action = new ProcessSentMessageAction();
- final Bundle params = action.actionParameters;
- params.putBoolean(KEY_SMS, false);
- params.putBoolean(KEY_SENT_BY_PLATFORM, true);
- params.putString(KEY_MESSAGE_ID, extras.getString(SendMessageAction.EXTRA_MESSAGE_ID));
- params.putParcelable(KEY_MESSAGE_URI, messageUri);
- params.putParcelable(KEY_UPDATED_MESSAGE_URI,
- extras.getParcelable(SendMessageAction.EXTRA_UPDATED_MESSAGE_URI));
- params.putInt(KEY_SUB_ID,
- extras.getInt(SendMessageAction.KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
- params.putInt(KEY_RESULT_CODE, resultCode);
- params.putInt(KEY_HTTP_STATUS_CODE, extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0));
- params.putParcelable(KEY_CONTENT_URI,
- extras.getParcelable(SendMessageAction.EXTRA_CONTENT_URI));
- params.putByteArray(KEY_RESPONSE, extras.getByteArray(SmsManager.EXTRA_MMS_DATA));
- params.putBoolean(KEY_RESPONSE_IMPORTANT,
- extras.getBoolean(SendMessageAction.EXTRA_RESPONSE_IMPORTANT));
- action.start();
- }
-
- public static void processMessageSentFastFailed(final String messageId,
- final Uri messageUri, final Uri updatedMessageUri, final int subId, final boolean isSms,
- final int status, final int rawStatus, final int resultCode) {
- final ProcessSentMessageAction action = new ProcessSentMessageAction();
- final Bundle params = action.actionParameters;
- params.putBoolean(KEY_SMS, isSms);
- params.putBoolean(KEY_SENT_BY_PLATFORM, false);
- params.putString(KEY_MESSAGE_ID, messageId);
- params.putParcelable(KEY_MESSAGE_URI, messageUri);
- params.putParcelable(KEY_UPDATED_MESSAGE_URI, updatedMessageUri);
- params.putInt(KEY_SUB_ID, subId);
- params.putInt(KEY_STATUS, status);
- params.putInt(KEY_RAW_STATUS, rawStatus);
- params.putInt(KEY_RESULT_CODE, resultCode);
- action.start();
- }
-
- private ProcessSentMessageAction() {
- // Callers must use one of the static methods above
- }
-
- /**
- * Update message status to reflect success or failure
- * Can also update the message itself if a "final" message is now available from telephony db
- */
- @Override
- protected Object executeAction() {
- final Context context = Factory.get().getApplicationContext();
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
- final Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI);
- final Uri updatedMessageUri = actionParameters.getParcelable(KEY_UPDATED_MESSAGE_URI);
- final boolean isSms = actionParameters.getBoolean(KEY_SMS);
- final boolean sentByPlatform = actionParameters.getBoolean(KEY_SENT_BY_PLATFORM);
-
- int status = actionParameters.getInt(KEY_STATUS, MmsUtils.MMS_REQUEST_MANUAL_RETRY);
- int rawStatus = actionParameters.getInt(KEY_RAW_STATUS,
- MmsUtils.PDU_HEADER_VALUE_UNDEFINED);
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
-
- if (sentByPlatform) {
- // Delete temporary file backing the contentUri passed to MMS service
- final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI);
- Assert.isTrue(contentUri != null);
- final File tempFile = MmsFileProvider.getFile(contentUri);
- long messageSize = 0;
- if (tempFile.exists()) {
- messageSize = tempFile.length();
- tempFile.delete();
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "ProcessSentMessageAction: Deleted temp file with outgoing "
- + "MMS pdu: " + contentUri);
- }
- }
-
- final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
- final boolean responseImportant = actionParameters.getBoolean(KEY_RESPONSE_IMPORTANT);
- if (resultCode == Activity.RESULT_OK) {
- if (responseImportant) {
- // Get the status from the response PDU and update telephony
- final byte[] response = actionParameters.getByteArray(KEY_RESPONSE);
- final SendConf sendConf = MmsSender.parseSendConf(response, subId);
- if (sendConf != null) {
- final MmsUtils.StatusPlusUri result =
- MmsUtils.updateSentMmsMessageStatus(context, messageUri, sendConf);
- status = result.status;
- rawStatus = result.rawStatus;
- }
- }
- } else {
- String errorMsg = "ProcessSentMessageAction: Platform returned error resultCode: "
- + resultCode;
- final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
- if (httpStatusCode != 0) {
- errorMsg += (", HTTP status code: " + httpStatusCode);
- }
- LogUtil.w(TAG, errorMsg);
- status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode);
-
- // Check for MMS messages that failed because they exceeded the maximum size,
- // indicated by an I/O error from the platform.
- if (resultCode == SmsManager.MMS_ERROR_IO_ERROR) {
- if (messageSize > MmsConfig.get(subId).getMaxMessageSize()) {
- rawStatus = MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG;
- }
- }
- }
- }
- if (messageId != null) {
- final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
- final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
- processResult(
- messageId, updatedMessageUri, status, rawStatus, isSms, this, subId,
- resultCode, httpStatusCode);
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "ProcessSentMessageAction: No sent message to process (it was "
- + "probably a notify response for an MMS download)");
- }
- }
- return null;
- }
-
- static void processResult(final String messageId, Uri updatedMessageUri, int status,
- final int rawStatus, final boolean isSms, final Action processingAction,
- final int subId, final int resultCode, final int httpStatusCode) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
- final MessageData originalMessage = message;
- if (message == null) {
- LogUtil.w(TAG, "ProcessSentMessageAction: Sent message " + messageId
- + " missing from local database");
- return;
- }
- final String conversationId = message.getConversationId();
- if (updatedMessageUri != null) {
- // Update message if we have newly written final message in the telephony db
- final MessageData update = MmsUtils.readSendingMmsMessage(updatedMessageUri,
- conversationId, message.getParticipantId(), message.getSelfId());
- if (update != null) {
- // Set message Id of final message to that of the existing place holder.
- update.updateMessageId(message.getMessageId());
- // Update image sizes.
- update.updateSizesForImageParts();
- // Temp attachments are no longer needed
- for (final MessagePartData part : message.getParts()) {
- part.destroySync();
- }
- message = update;
- // processResult will rewrite the complete message as part of update
- } else {
- updatedMessageUri = null;
- status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
- LogUtil.e(TAG, "ProcessSentMessageAction: Unable to read sending message");
- }
- }
-
- final long timestamp = System.currentTimeMillis();
- boolean failed;
- if (status == MmsUtils.MMS_REQUEST_SUCCEEDED) {
- message.markMessageSent(timestamp);
- failed = false;
- } else if (status == MmsUtils.MMS_REQUEST_AUTO_RETRY
- && message.getInResendWindow(timestamp)) {
- message.markMessageNotSent(timestamp);
- message.setRawTelephonyStatus(rawStatus);
- failed = false;
- } else {
- message.markMessageFailed(timestamp);
- message.setRawTelephonyStatus(rawStatus);
- message.setMessageSeen(false);
- failed = true;
- }
-
- // We have special handling for when a message to an emergency number fails. In this case,
- // we notify immediately of any failure (even if we auto-retry), and instruct the user to
- // try calling the emergency number instead.
- if (status != MmsUtils.MMS_REQUEST_SUCCEEDED) {
- final ArrayList<String> recipients =
- BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
- for (final String recipient : recipients) {
- if (PhoneNumberUtils.isEmergencyNumber(recipient)) {
- BugleNotifications.notifyEmergencySmsFailed(recipient, conversationId);
- message.markMessageFailedEmergencyNumber(timestamp);
- failed = true;
- break;
- }
- }
- }
-
- // Update the message status and optionally refresh the message with final parts/values.
- if (SendMessageAction.updateMessageAndStatus(isSms, message, updatedMessageUri, failed)) {
- // We shouldn't show any notifications if we're not allowed to modify Telephony for
- // this message.
- if (failed) {
- BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS);
- }
- BugleActionToasts.onSendMessageOrManualDownloadActionCompleted(
- conversationId, !failed, status, isSms, subId, true/*isSend*/);
- }
-
- LogUtil.i(TAG, "ProcessSentMessageAction: Done sending " + (isSms ? "SMS" : "MMS")
- + " message " + message.getMessageId()
- + " in conversation " + conversationId
- + "; status is " + MmsUtils.getRequestStatusDescription(status));
-
- // Whether we succeeded or failed we will check and maybe schedule some more work
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(
- status != MmsUtils.MMS_REQUEST_SUCCEEDED, processingAction);
- }
-
- private ProcessSentMessageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<ProcessSentMessageAction> CREATOR
- = new Parcelable.Creator<ProcessSentMessageAction>() {
- @Override
- public ProcessSentMessageAction createFromParcel(final Parcel in) {
- return new ProcessSentMessageAction(in);
- }
-
- @Override
- public ProcessSentMessageAction[] newArray(final int size) {
- return new ProcessSentMessageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java b/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java
deleted file mode 100644
index 7ac646b..0000000
--- a/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.LogUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-public class ReadDraftDataAction extends Action implements Parcelable {
-
- /**
- * Interface for ReadDraftDataAction listeners
- */
- public interface ReadDraftDataActionListener {
- @RunsOnMainThread
- abstract void onReadDraftDataSucceeded(final ReadDraftDataAction action,
- final Object data, final MessageData message,
- final ConversationListItemData conversation);
- @RunsOnMainThread
- abstract void onReadDraftDataFailed(final ReadDraftDataAction action, final Object data);
- }
-
- /**
- * Read draft message and associated data (with listener)
- */
- public static ReadDraftDataActionMonitor readDraftData(final String conversationId,
- final MessageData incomingDraft, final Object data,
- final ReadDraftDataActionListener listener) {
- final ReadDraftDataActionMonitor monitor = new ReadDraftDataActionMonitor(data,
- listener);
- final ReadDraftDataAction action = new ReadDraftDataAction(conversationId,
- incomingDraft, monitor.getActionKey());
- action.start(monitor);
- return monitor;
- }
-
- private static final String KEY_CONVERSATION_ID = "conversationId";
- private static final String KEY_INCOMING_DRAFT = "draftMessage";
-
- private ReadDraftDataAction(final String conversationId, final MessageData incomingDraft,
- final String actionKey) {
- super(actionKey);
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- actionParameters.putParcelable(KEY_INCOMING_DRAFT, incomingDraft);
- }
-
- @VisibleForTesting
- class DraftData {
- public final MessageData message;
- public final ConversationListItemData conversation;
-
- DraftData(final MessageData message, final ConversationListItemData conversation) {
- this.message = message;
- this.conversation = conversation;
- }
- }
-
- @Override
- protected Object executeAction() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- final MessageData incomingDraft = actionParameters.getParcelable(KEY_INCOMING_DRAFT);
- final ConversationListItemData conversation =
- ConversationListItemData.getExistingConversation(db, conversationId);
- MessageData message = null;
- if (conversation != null) {
- if (incomingDraft == null) {
- message = BugleDatabaseOperations.readDraftMessageData(db, conversationId,
- conversation.getSelfId());
- }
- if (message == null) {
- message = MessageData.createDraftMessage(conversationId, conversation.getSelfId(),
- incomingDraft);
- LogUtil.d(LogUtil.BUGLE_TAG, "ReadDraftMessage: created draft. "
- + "conversationId=" + conversationId
- + " selfId=" + conversation.getSelfId());
- } else {
- LogUtil.d(LogUtil.BUGLE_TAG, "ReadDraftMessage: read draft. "
- + "conversationId=" + conversationId
- + " selfId=" + conversation.getSelfId());
- }
- return new DraftData(message, conversation);
- }
- return null;
- }
-
- /**
- * An operation that notifies a listener upon completion
- */
- public static class ReadDraftDataActionMonitor extends ActionMonitor
- implements ActionCompletedListener {
-
- private final ReadDraftDataActionListener mListener;
-
- ReadDraftDataActionMonitor(final Object data,
- final ReadDraftDataActionListener completed) {
- super(STATE_CREATED, generateUniqueActionKey("ReadDraftDataAction"), data);
- setCompletedListener(this);
- mListener = completed;
- }
-
- @Override
- public void onActionSucceeded(final ActionMonitor monitor,
- final Action action, final Object data, final Object result) {
- final DraftData draft = (DraftData) result;
- if (draft == null) {
- mListener.onReadDraftDataFailed((ReadDraftDataAction) action, data);
- } else {
- mListener.onReadDraftDataSucceeded((ReadDraftDataAction) action, data,
- draft.message, draft.conversation);
- }
- }
-
- @Override
- public void onActionFailed(final ActionMonitor monitor,
- final Action action, final Object data, final Object result) {
- Assert.fail("Reading draft should not fail");
- }
- }
-
- private ReadDraftDataAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<ReadDraftDataAction> CREATOR
- = new Parcelable.Creator<ReadDraftDataAction>() {
- @Override
- public ReadDraftDataAction createFromParcel(final Parcel in) {
- return new ReadDraftDataAction(in);
- }
-
- @Override
- public ReadDraftDataAction[] newArray(final int size) {
- return new ReadDraftDataAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java b/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java
deleted file mode 100644
index 6794b17..0000000
--- a/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DataModelException;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.sms.DatabaseMessages;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.LogUtil;
-
-import java.util.List;
-
-/**
- * Action used to "receive" an incoming message
- */
-public class ReceiveMmsMessageAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- private static final String KEY_SUB_ID = "sub_id";
- private static final String KEY_PUSH_DATA = "push_data";
- private static final String KEY_TRANSACTION_ID = "transaction_id";
- private static final String KEY_CONTENT_LOCATION = "content_location";
-
- /**
- * Create a message received from a particular number in a particular conversation
- */
- public ReceiveMmsMessageAction(final int subId, final byte[] pushData) {
- actionParameters.putInt(KEY_SUB_ID, subId);
- actionParameters.putByteArray(KEY_PUSH_DATA, pushData);
- }
-
- @Override
- protected Object executeAction() {
- final Context context = Factory.get().getApplicationContext();
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
- final byte[] pushData = actionParameters.getByteArray(KEY_PUSH_DATA);
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- // Write received message to telephony DB
- MessageData message = null;
- final ParticipantData self = BugleDatabaseOperations.getOrCreateSelf(db, subId);
-
- final long received = System.currentTimeMillis();
- // Inform sync that message has been added at local received timestamp
- final SyncManager syncManager = DataModel.get().getSyncManager();
- syncManager.onNewMessageInserted(received);
-
- // TODO: Should use local time to set received time in MMS message
- final DatabaseMessages.MmsMessage mms = MmsUtils.processReceivedPdu(
- context, pushData, self.getSubId(), self.getNormalizedDestination());
-
- if (mms != null) {
- final List<String> recipients = MmsUtils.getRecipientsByThread(mms.mThreadId);
- String from = MmsUtils.getMmsSender(recipients, mms.getUri());
- if (from == null) {
- LogUtil.w(TAG, "Received an MMS without sender address; using unknown sender.");
- from = ParticipantData.getUnknownSenderDestination();
- }
- final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(
- from, subId);
- final boolean blocked = BugleDatabaseOperations.isBlockedDestination(
- db, rawSender.getNormalizedDestination());
- final boolean autoDownload = (!blocked && MmsUtils.allowMmsAutoRetrieve(subId));
- final String conversationId =
- BugleDatabaseOperations.getOrCreateConversationFromThreadId(db, mms.mThreadId,
- blocked, subId);
-
- final boolean messageInFocusedConversation =
- DataModel.get().isFocusedConversation(conversationId);
- final boolean messageInObservableConversation =
- DataModel.get().isNewMessageObservable(conversationId);
-
- // TODO: Also write these values to the telephony provider
- mms.mRead = messageInFocusedConversation;
- mms.mSeen = messageInObservableConversation || blocked;
-
- // Write received placeholder message to our DB
- db.beginTransaction();
- try {
- final String participantId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender);
- final String selfId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
-
- message = MmsUtils.createMmsMessage(mms, conversationId, participantId, selfId,
- (autoDownload ? MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD :
- MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD));
- // Write the message
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
-
- if (!autoDownload) {
- BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
- conversationId, message.getMessageId(), message.getReceivedTimeStamp(),
- blocked, true /* shouldAutoSwitchSelfId */);
- final ParticipantData sender = ParticipantData .getFromId(
- db, participantId);
- BugleActionToasts.onMessageReceived(conversationId, sender, message);
- }
- // else update the conversation once we have downloaded final message (or failed)
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- // Update conversation if not immediately initiating a download
- if (!autoDownload) {
- MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
- MessagingContentProvider.notifyPartsChanged();
-
- // Show a notification to let the user know a new message has arrived
- BugleNotifications.update(false/*silent*/, conversationId,
- BugleNotifications.UPDATE_ALL);
-
- // Send the NotifyRespInd with DEFERRED status since no auto download
- actionParameters.putString(KEY_TRANSACTION_ID, mms.mTransactionId);
- actionParameters.putString(KEY_CONTENT_LOCATION, mms.mContentLocation);
- requestBackgroundWork();
- }
-
- LogUtil.i(TAG, "ReceiveMmsMessageAction: Received MMS message " + message.getMessageId()
- + " in conversation " + message.getConversationId()
- + ", uri = " + message.getSmsMessageUri());
- } else {
- LogUtil.e(TAG, "ReceiveMmsMessageAction: Skipping processing of incoming PDU");
- }
-
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
-
- return message;
- }
-
- @Override
- protected Bundle doBackgroundWork() throws DataModelException {
- final Context context = Factory.get().getApplicationContext();
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
- final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
- final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
- MmsUtils.sendNotifyResponseForMmsDownload(
- context,
- subId,
- MmsUtils.stringToBytes(transactionId, "UTF-8"),
- contentLocation,
- PduHeaders.STATUS_DEFERRED);
- // We don't need to return anything.
- return null;
- }
-
- private ReceiveMmsMessageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<ReceiveMmsMessageAction> CREATOR
- = new Parcelable.Creator<ReceiveMmsMessageAction>() {
- @Override
- public ReceiveMmsMessageAction createFromParcel(final Parcel in) {
- return new ReceiveMmsMessageAction(in);
- }
-
- @Override
- public ReceiveMmsMessageAction[] newArray(final int size) {
- return new ReceiveMmsMessageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java b/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java
deleted file mode 100644
index 5ffb35d..0000000
--- a/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony.Sms;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Action used to "receive" an incoming message
- */
-public class ReceiveSmsMessageAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- private static final String KEY_MESSAGE_VALUES = "message_values";
-
- /**
- * Create a message received from a particular number in a particular conversation
- */
- public ReceiveSmsMessageAction(final ContentValues messageValues) {
- actionParameters.putParcelable(KEY_MESSAGE_VALUES, messageValues);
- }
-
- @Override
- protected Object executeAction() {
- final Context context = Factory.get().getApplicationContext();
- final ContentValues messageValues = actionParameters.getParcelable(KEY_MESSAGE_VALUES);
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- // Get the SIM subscription ID
- Integer subId = messageValues.getAsInteger(Sms.SUBSCRIPTION_ID);
- if (subId == null) {
- subId = ParticipantData.DEFAULT_SELF_SUB_ID;
- }
- // Make sure we have a sender address
- String address = messageValues.getAsString(Sms.ADDRESS);
- if (TextUtils.isEmpty(address)) {
- LogUtil.w(TAG, "Received an SMS without an address; using unknown sender.");
- address = ParticipantData.getUnknownSenderDestination();
- messageValues.put(Sms.ADDRESS, address);
- }
- final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(
- address, subId);
-
- // TODO: Should use local timestamp for this?
- final long received = messageValues.getAsLong(Sms.DATE);
- // Inform sync that message has been added at local received timestamp
- final SyncManager syncManager = DataModel.get().getSyncManager();
- syncManager.onNewMessageInserted(received);
-
- // Make sure we've got a thread id
- final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(context, address);
- messageValues.put(Sms.THREAD_ID, threadId);
- final boolean blocked = BugleDatabaseOperations.isBlockedDestination(
- db, rawSender.getNormalizedDestination());
- final String conversationId = BugleDatabaseOperations.
- getOrCreateConversationFromRecipient(db, threadId, blocked, rawSender);
-
- final boolean messageInFocusedConversation =
- DataModel.get().isFocusedConversation(conversationId);
- final boolean messageInObservableConversation =
- DataModel.get().isNewMessageObservable(conversationId);
-
- MessageData message = null;
- // Only the primary user gets to insert the message into the telephony db and into bugle's
- // db. The secondary user goes through this path, but skips doing the actual insert. It
- // goes through this path because it needs to compute messageInFocusedConversation in order
- // to calculate whether to skip the notification and play a soft sound if the user is
- // already in the conversation.
- if (!OsUtil.isSecondaryUser()) {
- final boolean read = messageValues.getAsBoolean(Sms.Inbox.READ)
- || messageInFocusedConversation;
- // If you have read it you have seen it
- final boolean seen = read || messageInObservableConversation || blocked;
- messageValues.put(Sms.Inbox.READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
-
- // incoming messages are marked as seen in the telephony db
- messageValues.put(Sms.Inbox.SEEN, 1);
-
- // Insert into telephony
- final Uri messageUri = context.getContentResolver().insert(Sms.Inbox.CONTENT_URI,
- messageValues);
-
- if (messageUri != null) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "ReceiveSmsMessageAction: Inserted SMS message into telephony, "
- + "uri = " + messageUri);
- }
- } else {
- LogUtil.e(TAG, "ReceiveSmsMessageAction: Failed to insert SMS into telephony!");
- }
-
- final String text = messageValues.getAsString(Sms.BODY);
- final String subject = messageValues.getAsString(Sms.SUBJECT);
- final long sent = messageValues.getAsLong(Sms.DATE_SENT);
- final ParticipantData self = ParticipantData.getSelfParticipant(subId);
- final Integer pathPresent = messageValues.getAsInteger(Sms.REPLY_PATH_PRESENT);
- final String smsServiceCenter = messageValues.getAsString(Sms.SERVICE_CENTER);
- String conversationServiceCenter = null;
- // Only set service center if message REPLY_PATH_PRESENT = 1
- if (pathPresent != null && pathPresent == 1 && !TextUtils.isEmpty(smsServiceCenter)) {
- conversationServiceCenter = smsServiceCenter;
- }
- db.beginTransaction();
- try {
- final String participantId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender);
- final String selfId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
-
- message = MessageData.createReceivedSmsMessage(messageUri, conversationId,
- participantId, selfId, text, subject, sent, received, seen, read);
-
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
-
- BugleDatabaseOperations.updateConversationMetadataInTransaction(db, conversationId,
- message.getMessageId(), message.getReceivedTimeStamp(), blocked,
- conversationServiceCenter, true /* shouldAutoSwitchSelfId */);
-
- final ParticipantData sender = ParticipantData.getFromId(db, participantId);
- BugleActionToasts.onMessageReceived(conversationId, sender, message);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- LogUtil.i(TAG, "ReceiveSmsMessageAction: Received SMS message " + message.getMessageId()
- + " in conversation " + message.getConversationId()
- + ", uri = " + messageUri);
-
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "ReceiveSmsMessageAction: Not inserting received SMS message for "
- + "secondary user.");
- }
- }
- // Show a notification to let the user know a new message has arrived
- BugleNotifications.update(false/*silent*/, conversationId, BugleNotifications.UPDATE_ALL);
-
- MessagingContentProvider.notifyMessagesChanged(conversationId);
- MessagingContentProvider.notifyPartsChanged();
-
- return message;
- }
-
- private ReceiveSmsMessageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<ReceiveSmsMessageAction> CREATOR
- = new Parcelable.Creator<ReceiveSmsMessageAction>() {
- @Override
- public ReceiveSmsMessageAction createFromParcel(final Parcel in) {
- return new ReceiveSmsMessageAction(in);
- }
-
- @Override
- public ReceiveSmsMessageAction[] newArray(final int size) {
- return new ReceiveSmsMessageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java b/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java
deleted file mode 100644
index e899b0c..0000000
--- a/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.app.PendingIntent;
-import android.content.ContentValues;
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Action to manually start an MMS download (after failed or manual mms download)
- */
-public class RedownloadMmsAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- private static final int REQUEST_CODE_PENDING_INTENT = 102;
-
- /**
- * Download an MMS message
- */
- public static void redownloadMessage(final String messageId) {
- final RedownloadMmsAction action = new RedownloadMmsAction(messageId);
- action.start();
- }
-
- /**
- * Get a pending intent of for downloading an MMS
- */
- public static PendingIntent getPendingIntentForRedownloadMms(
- final Context context, final String messageId) {
- final Action action = new RedownloadMmsAction(messageId);
- return ActionService.makeStartActionPendingIntent(context,
- action, REQUEST_CODE_PENDING_INTENT, false /*launchesAnActivity*/);
- }
-
- // Core parameters needed for all types of message
- private static final String KEY_MESSAGE_ID = "message_id";
-
- /**
- * Constructor used for retrying sending in the background (only message id available)
- */
- RedownloadMmsAction(final String messageId) {
- super();
- actionParameters.putString(KEY_MESSAGE_ID, messageId);
- }
-
- /**
- * Read message from database and change status to allow downloading
- */
- @Override
- protected Object executeAction() {
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
- // Check message can be redownloaded
- if (message != null && message.canRedownloadMessage()) {
- final long timestamp = System.currentTimeMillis();
-
- final ContentValues values = new ContentValues(2);
- values.put(DatabaseHelper.MessageColumns.STATUS,
- MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD);
- values.put(DatabaseHelper.MessageColumns.RETRY_START_TIMESTAMP, timestamp);
-
- // Row must exist as was just loaded above (on ActionService thread)
- BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values);
-
- MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
-
- // Whether we succeeded or failed we will check and maybe schedule some more work
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
- } else {
- message = null;
- LogUtil.e(LogUtil.BUGLE_TAG,
- "Attempt to download a missing or un-redownloadable message");
- }
- // Immediately update the notifications in case we came from the download action from a
- // heads-up notification. This will dismiss the heads-up notification.
- BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL);
- return message;
- }
-
- private RedownloadMmsAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<RedownloadMmsAction> CREATOR
- = new Parcelable.Creator<RedownloadMmsAction>() {
- @Override
- public RedownloadMmsAction createFromParcel(final Parcel in) {
- return new RedownloadMmsAction(in);
- }
-
- @Override
- public RedownloadMmsAction[] newArray(final int size) {
- return new RedownloadMmsAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/ResendMessageAction.java b/src/com/android/messaging/datamodel/action/ResendMessageAction.java
deleted file mode 100644
index 2201965..0000000
--- a/src/com/android/messaging/datamodel/action/ResendMessageAction.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Action used to manually resend an outgoing message
- */
-public class ResendMessageAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- /**
- * Manual send of existing message (no listener)
- */
- public static void resendMessage(final String messageId) {
- final ResendMessageAction action = new ResendMessageAction(messageId);
- action.start();
- }
-
- // Core parameters needed for all types of message
- private static final String KEY_MESSAGE_ID = "message_id";
-
- /**
- * Constructor used for retrying sending in the background (only message id available)
- */
- ResendMessageAction(final String messageId) {
- super();
- actionParameters.putString(KEY_MESSAGE_ID, messageId);
- }
-
- /**
- * Read message from database and change status to allow sending
- */
- @Override
- protected Object executeAction() {
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
- // Check message can be resent
- if (message != null && message.canResendMessage()) {
- final boolean isMms = message.getIsMms();
- long timestamp = System.currentTimeMillis();
- if (isMms) {
- // MMS expects timestamp rounded to nearest second
- timestamp = 1000 * ((timestamp + 500) / 1000);
- }
-
- LogUtil.i(TAG, "ResendMessageAction: Resending message " + messageId
- + "; changed timestamp from " + message.getReceivedTimeStamp() + " to "
- + timestamp);
-
- final ContentValues values = new ContentValues();
- values.put(MessageColumns.STATUS, MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND);
- values.put(MessageColumns.RECEIVED_TIMESTAMP, timestamp);
- values.put(MessageColumns.SENT_TIMESTAMP, timestamp);
- values.put(MessageColumns.RETRY_START_TIMESTAMP, timestamp);
-
- // Row must exist as was just loaded above (on ActionService thread)
- BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values);
-
- MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
-
- // Whether we succeeded or failed we will check and maybe schedule some more work
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
-
- return message;
- } else {
- String error = "ResendMessageAction: Cannot resend message " + messageId + "; ";
- if (message != null) {
- error += ("status = " + MessageData.getStatusDescription(message.getStatus()));
- } else {
- error += "not found in database";
- }
- LogUtil.e(TAG, error);
- }
-
- return null;
- }
-
- private ResendMessageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<ResendMessageAction> CREATOR
- = new Parcelable.Creator<ResendMessageAction>() {
- @Override
- public ResendMessageAction createFromParcel(final Parcel in) {
- return new ResendMessageAction(in);
- }
-
- @Override
- public ResendMessageAction[] newArray(final int size) {
- return new ResendMessageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/SendMessageAction.java b/src/com/android/messaging/datamodel/action/SendMessageAction.java
deleted file mode 100644
index d7ebe8f..0000000
--- a/src/com/android/messaging/datamodel/action/SendMessageAction.java
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-
-/**
- * Action used to send an outgoing message. It writes MMS messages to the telephony db
- * ({@link InsertNewMessageAction}) writes SMS messages to the telephony db). It also
- * initiates the actual sending. It will all be used for re-sending a failed message.
- * NOTE: This action must queue a ProcessPendingMessagesAction when it is done (success or failure).
- * <p>
- * This class is public (not package-private) because the SMS/MMS (e.g. MmsUtils) classes need to
- * access the EXTRA_* fields for setting up the 'sent' pending intent.
- */
-public class SendMessageAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- /**
- * Queue sending of existing message (can only be called during execute of action)
- */
- static boolean queueForSendInBackground(final String messageId,
- final Action processingAction) {
- final SendMessageAction action = new SendMessageAction();
- return action.queueAction(messageId, processingAction);
- }
-
- public static final boolean DEFAULT_DELIVERY_REPORT_MODE = false;
- public static final int MAX_SMS_RETRY = 3;
-
- // Core parameters needed for all types of message
- private static final String KEY_MESSAGE_ID = "message_id";
- private static final String KEY_MESSAGE = "message";
- private static final String KEY_MESSAGE_URI = "message_uri";
- private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
-
- // For sms messages a few extra values are included in the bundle
- private static final String KEY_RECIPIENT = "recipient";
- private static final String KEY_RECIPIENTS = "recipients";
- private static final String KEY_SMS_SERVICE_CENTER = "sms_service_center";
-
- // Values we attach to the pending intent that's fired when the message is sent.
- // Only applicable when sending via the platform APIs on L+.
- public static final String KEY_SUB_ID = "sub_id";
- public static final String EXTRA_MESSAGE_ID = "message_id";
- public static final String EXTRA_UPDATED_MESSAGE_URI = "updated_message_uri";
- public static final String EXTRA_CONTENT_URI = "content_uri";
- public static final String EXTRA_RESPONSE_IMPORTANT = "response_important";
-
- /**
- * Constructor used for retrying sending in the background (only message id available)
- */
- private SendMessageAction() {
- super();
- }
-
- /**
- * Read message from database and queue actual sending
- */
- private boolean queueAction(final String messageId, final Action processingAction) {
- actionParameters.putString(KEY_MESSAGE_ID, messageId);
-
- final long timestamp = System.currentTimeMillis();
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
- // Check message can be resent
- if (message != null && message.canSendMessage()) {
- final boolean isSms = (message.getProtocol() == MessageData.PROTOCOL_SMS);
-
- final ParticipantData self = BugleDatabaseOperations.getExistingParticipant(
- db, message.getSelfId());
- final Uri messageUri = message.getSmsMessageUri();
- final String conversationId = message.getConversationId();
-
- // Update message status
- if (message.getYetToSend()) {
- // Initial sending of message
- message.markMessageSending(timestamp);
- } else {
- // Automatic resend of message
- message.markMessageResending(timestamp);
- }
- if (!updateMessageAndStatus(isSms, message, null /* messageUri */, false /*notify*/)) {
- // If message is missing in the telephony database we don't need to send it
- return false;
- }
-
- final ArrayList<String> recipients =
- BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
-
- // Update action state with parameters needed for background sending
- actionParameters.putParcelable(KEY_MESSAGE_URI, messageUri);
- actionParameters.putParcelable(KEY_MESSAGE, message);
- actionParameters.putStringArrayList(KEY_RECIPIENTS, recipients);
- actionParameters.putInt(KEY_SUB_ID, self.getSubId());
- actionParameters.putString(KEY_SUB_PHONE_NUMBER, self.getNormalizedDestination());
-
- if (isSms) {
- final String smsc = BugleDatabaseOperations.getSmsServiceCenterForConversation(
- db, conversationId);
- actionParameters.putString(KEY_SMS_SERVICE_CENTER, smsc);
-
- if (recipients.size() == 1) {
- final String recipient = recipients.get(0);
-
- actionParameters.putString(KEY_RECIPIENT, recipient);
- // Queue actual sending for SMS
- processingAction.requestBackgroundWork(this);
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SendMessageAction: Queued SMS message " + messageId
- + " for sending");
- }
- return true;
- } else {
- LogUtil.wtf(TAG, "Trying to resend a broadcast SMS - not allowed");
- }
- } else {
- // Queue actual sending for MMS
- processingAction.requestBackgroundWork(this);
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SendMessageAction: Queued MMS message " + messageId
- + " for sending");
- }
- return true;
- }
- }
-
- return false;
- }
-
-
- /**
- * Never called
- */
- @Override
- protected Object executeAction() {
- Assert.fail("SendMessageAction must be queued rather than started");
- return null;
- }
-
- /**
- * Send message on background worker thread
- */
- @Override
- protected Bundle doBackgroundWork() {
- final MessageData message = actionParameters.getParcelable(KEY_MESSAGE);
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
- Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI);
- Uri updatedMessageUri = null;
- final boolean isSms = message.getProtocol() == MessageData.PROTOCOL_SMS;
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
- final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER);
-
- LogUtil.i(TAG, "SendMessageAction: Sending " + (isSms ? "SMS" : "MMS") + " message "
- + messageId + " in conversation " + message.getConversationId());
-
- int status;
- int rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- int resultCode = MessageData.UNKNOWN_RESULT_CODE;
- if (isSms) {
- Assert.notNull(messageUri);
- final String recipient = actionParameters.getString(KEY_RECIPIENT);
- final String messageText = message.getMessageText();
- final String smsServiceCenter = actionParameters.getString(KEY_SMS_SERVICE_CENTER);
- final boolean deliveryReportRequired = MmsUtils.isDeliveryReportRequired(subId);
-
- status = MmsUtils.sendSmsMessage(recipient, messageText, messageUri, subId,
- smsServiceCenter, deliveryReportRequired);
- } else {
- final Context context = Factory.get().getApplicationContext();
- final ArrayList<String> recipients =
- actionParameters.getStringArrayList(KEY_RECIPIENTS);
- if (messageUri == null) {
- final long timestamp = message.getReceivedTimeStamp();
-
- // Inform sync that message has been added at local received timestamp
- final SyncManager syncManager = DataModel.get().getSyncManager();
- syncManager.onNewMessageInserted(timestamp);
-
- // For MMS messages first need to write to telephony (resizing images if needed)
- updatedMessageUri = MmsUtils.insertSendingMmsMessage(context, recipients,
- message, subId, subPhoneNumber, timestamp);
- if (updatedMessageUri != null) {
- messageUri = updatedMessageUri;
- // To prevent Sync seeing inconsistent state must write to DB on this thread
- updateMessageUri(messageId, updatedMessageUri);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SendMessageAction: Updated message " + messageId
- + " with new uri " + messageUri);
- }
- }
- }
- if (messageUri != null) {
- // Actually send the MMS
- final Bundle extras = new Bundle();
- extras.putString(EXTRA_MESSAGE_ID, messageId);
- extras.putParcelable(EXTRA_UPDATED_MESSAGE_URI, updatedMessageUri);
- final MmsUtils.StatusPlusUri result = MmsUtils.sendMmsMessage(context, subId,
- messageUri, extras);
- if (result == MmsUtils.STATUS_PENDING) {
- // Async send, so no status yet
- LogUtil.d(TAG, "SendMessageAction: Sending MMS message " + messageId
- + " asynchronously; waiting for callback to finish processing");
- return null;
- }
- status = result.status;
- rawStatus = result.rawStatus;
- resultCode = result.resultCode;
- } else {
- status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
- }
- }
-
- // When we fast-fail before calling the MMS lib APIs (e.g. airplane mode,
- // sending message is deleted).
- ProcessSentMessageAction.processMessageSentFastFailed(messageId, messageUri,
- updatedMessageUri, subId, isSms, status, rawStatus, resultCode);
- return null;
- }
-
- private void updateMessageUri(final String messageId, final Uri updatedMessageUri) {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- try {
- final ContentValues values = new ContentValues();
- values.put(MessageColumns.SMS_MESSAGE_URI, updatedMessageUri.toString());
- BugleDatabaseOperations.updateMessageRow(db, messageId, values);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- @Override
- protected Object processBackgroundResponse(final Bundle response) {
- // Nothing to do here, post-send tasks handled by ProcessSentMessageAction
- return null;
- }
-
- /**
- * Update message status to reflect success or failure
- */
- @Override
- protected Object processBackgroundFailure() {
- final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
- final MessageData message = actionParameters.getParcelable(KEY_MESSAGE);
- final boolean isSms = message.getProtocol() == MessageData.PROTOCOL_SMS;
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
- final int resultCode = actionParameters.getInt(ProcessSentMessageAction.KEY_RESULT_CODE);
- final int httpStatusCode =
- actionParameters.getInt(ProcessSentMessageAction.KEY_HTTP_STATUS_CODE);
-
- ProcessSentMessageAction.processResult(messageId, null /* updatedMessageUri */,
- MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
- isSms, this, subId, resultCode, httpStatusCode);
-
- // Whether we succeeded or failed we will check and maybe schedule some more work
- ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(true, this);
-
- return null;
- }
-
- /**
- * Update the message status (and message itself if necessary)
- * @param isSms whether this is an SMS or MMS
- * @param message message to update
- * @param updatedMessageUri message uri for newly-inserted messages; null otherwise
- * @param clearSeen whether the message 'seen' status should be reset if error occurs
- */
- public static boolean updateMessageAndStatus(final boolean isSms, final MessageData message,
- final Uri updatedMessageUri, final boolean clearSeen) {
- final Context context = Factory.get().getApplicationContext();
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- // TODO: We're optimistically setting the type/box of outgoing messages to
- // 'SENT' even before they actually are. We should technically be using QUEUED or OUTBOX
- // instead, but if we do that, it's possible that the Messaging app will try to send them
- // as part of its clean-up logic that runs when it starts (http://b/18155366).
- //
- // We also use the wrong status when inserting queued SMS messages in
- // InsertNewMessageAction.insertBroadcastSmsMessage and insertSendingSmsMessage (should be
- // QUEUED or OUTBOX), and in MmsUtils.insertSendReq (should be OUTBOX).
-
- boolean updatedTelephony = true;
- int messageBox;
- int type;
- switch(message.getStatus()) {
- case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
- case MessageData.BUGLE_STATUS_OUTGOING_DELIVERED:
- type = Sms.MESSAGE_TYPE_SENT;
- messageBox = Mms.MESSAGE_BOX_SENT;
- break;
- case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
- case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
- type = Sms.MESSAGE_TYPE_SENT;
- messageBox = Mms.MESSAGE_BOX_SENT;
- break;
- case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
- case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
- type = Sms.MESSAGE_TYPE_SENT;
- messageBox = Mms.MESSAGE_BOX_SENT;
- break;
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
- type = Sms.MESSAGE_TYPE_FAILED;
- messageBox = Mms.MESSAGE_BOX_FAILED;
- break;
- default:
- type = Sms.MESSAGE_TYPE_ALL;
- messageBox = Mms.MESSAGE_BOX_ALL;
- break;
- }
- // First in the telephony DB
- if (isSms) {
- // Ignore update message Uri
- if (type != Sms.MESSAGE_TYPE_ALL) {
- if (!MmsUtils.updateSmsMessageSendingStatus(context, message.getSmsMessageUri(),
- type, message.getReceivedTimeStamp())) {
- message.markMessageFailed(message.getSentTimeStamp());
- updatedTelephony = false;
- }
- }
- } else if (message.getSmsMessageUri() != null) {
- if (messageBox != Mms.MESSAGE_BOX_ALL) {
- if (!MmsUtils.updateMmsMessageSendingStatus(context, message.getSmsMessageUri(),
- messageBox, message.getReceivedTimeStamp())) {
- message.markMessageFailed(message.getSentTimeStamp());
- updatedTelephony = false;
- }
- }
- }
- if (updatedTelephony) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SendMessageAction: Updated " + (isSms ? "SMS" : "MMS")
- + " message " + message.getMessageId()
- + " in telephony (" + message.getSmsMessageUri() + ")");
- }
- } else {
- LogUtil.w(TAG, "SendMessageAction: Failed to update " + (isSms ? "SMS" : "MMS")
- + " message " + message.getMessageId()
- + " in telephony (" + message.getSmsMessageUri() + "); marking message failed");
- }
-
- // Update the local DB
- db.beginTransaction();
- try {
- if (updatedMessageUri != null) {
- // Update all message and part fields
- BugleDatabaseOperations.updateMessageInTransaction(db, message);
- BugleDatabaseOperations.refreshConversationMetadataInTransaction(
- db, message.getConversationId(), false/* shouldAutoSwitchSelfId */,
- false/*archived*/);
- } else {
- final ContentValues values = new ContentValues();
- values.put(MessageColumns.STATUS, message.getStatus());
-
- if (clearSeen) {
- // When a message fails to send, the message needs to
- // be unseen to be selected as an error notification.
- values.put(MessageColumns.SEEN, 0);
- }
- values.put(MessageColumns.RECEIVED_TIMESTAMP, message.getReceivedTimeStamp());
- values.put(MessageColumns.RAW_TELEPHONY_STATUS, message.getRawTelephonyStatus());
-
- BugleDatabaseOperations.updateMessageRowIfExists(db, message.getMessageId(),
- values);
- }
- db.setTransactionSuccessful();
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SendMessageAction: Updated " + (isSms ? "SMS" : "MMS")
- + " message " + message.getMessageId() + " in local db. Timestamp = "
- + message.getReceivedTimeStamp());
- }
- } finally {
- db.endTransaction();
- }
-
- MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
- if (updatedMessageUri != null) {
- MessagingContentProvider.notifyPartsChanged();
- }
-
- return updatedTelephony;
- }
-
- private SendMessageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<SendMessageAction> CREATOR
- = new Parcelable.Creator<SendMessageAction>() {
- @Override
- public SendMessageAction createFromParcel(final Parcel in) {
- return new SendMessageAction(in);
- }
-
- @Override
- public SendMessageAction[] newArray(final int size) {
- return new SendMessageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/SyncCursorPair.java b/src/com/android/messaging/datamodel/action/SyncCursorPair.java
deleted file mode 100644
index b3a2676..0000000
--- a/src/com/android/messaging/datamodel/action/SyncCursorPair.java
+++ /dev/null
@@ -1,712 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.support.v4.util.LongSparseArray;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.SyncManager.ThreadInfoCache;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.sms.DatabaseMessages;
-import com.android.messaging.sms.DatabaseMessages.DatabaseMessage;
-import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage;
-import com.android.messaging.sms.DatabaseMessages.MmsMessage;
-import com.android.messaging.sms.DatabaseMessages.SmsMessage;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.google.common.collect.Sets;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Class holding a pair of cursors - one for local db and one for telephony provider - allowing
- * synchronous stepping through messages as part of sync.
- */
-class SyncCursorPair {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- static final long SYNC_COMPLETE = -1L;
- static final long SYNC_STARTING = Long.MAX_VALUE;
-
- private CursorIterator mLocalCursorIterator;
- private CursorIterator mRemoteCursorsIterator;
-
- private final String mLocalSelection;
- private final String mRemoteSmsSelection;
- private final String mRemoteMmsSelection;
-
- /**
- * Check if SMS has been synchronized. We compare the counts of messages on both
- * sides and return true if they are equal.
- *
- * Note that this may not be the most reliable way to tell if messages are in sync.
- * For example, the local misses one message and has one obsolete message.
- * However, we have background sms sync once a while, also some other events might
- * trigger a full sync. So we will eventually catch up. And this should be rare to
- * happen.
- *
- * @return If sms is in sync with telephony sms/mms providers
- */
- static boolean allSynchronized(final DatabaseWrapper db) {
- return isSynchronized(db, LOCAL_MESSAGES_SELECTION, null,
- getSmsTypeSelectionSql(), null, getMmsTypeSelectionSql(), null);
- }
-
- SyncCursorPair(final long lowerBound, final long upperBound) {
- mLocalSelection = getTimeConstrainedQuery(
- LOCAL_MESSAGES_SELECTION,
- MessageColumns.RECEIVED_TIMESTAMP,
- lowerBound,
- upperBound,
- null /* threadColumn */, null /* threadId */);
- mRemoteSmsSelection = getTimeConstrainedQuery(
- getSmsTypeSelectionSql(),
- "date",
- lowerBound,
- upperBound,
- null /* threadColumn */, null /* threadId */);
- mRemoteMmsSelection = getTimeConstrainedQuery(
- getMmsTypeSelectionSql(),
- "date",
- ((lowerBound < 0) ? lowerBound : (lowerBound + 999) / 1000), /*seconds*/
- ((upperBound < 0) ? upperBound : (upperBound + 999) / 1000), /*seconds*/
- null /* threadColumn */, null /* threadId */);
- }
-
- SyncCursorPair(final long threadId, final String conversationId) {
- mLocalSelection = getTimeConstrainedQuery(
- LOCAL_MESSAGES_SELECTION,
- MessageColumns.RECEIVED_TIMESTAMP,
- -1L,
- -1L,
- MessageColumns.CONVERSATION_ID, conversationId);
- // Find all SMS messages (excluding drafts) within the sync window
- mRemoteSmsSelection = getTimeConstrainedQuery(
- getSmsTypeSelectionSql(),
- "date",
- -1L,
- -1L,
- Sms.THREAD_ID, Long.toString(threadId));
- mRemoteMmsSelection = getTimeConstrainedQuery(
- getMmsTypeSelectionSql(),
- "date",
- -1L, /*seconds*/
- -1L, /*seconds*/
- Mms.THREAD_ID, Long.toString(threadId));
- }
-
- void query(final DatabaseWrapper db) {
- // Load local messages in the sync window
- mLocalCursorIterator = new LocalCursorIterator(db, mLocalSelection);
- // Load remote messages in the sync window
- mRemoteCursorsIterator = new RemoteCursorsIterator(mRemoteSmsSelection,
- mRemoteMmsSelection);
- }
-
- boolean isSynchronized(final DatabaseWrapper db) {
- return isSynchronized(db, mLocalSelection, null, mRemoteSmsSelection,
- null, mRemoteMmsSelection, null);
- }
-
- void close() {
- if (mLocalCursorIterator != null) {
- mLocalCursorIterator.close();
- }
- if (mRemoteCursorsIterator != null) {
- mRemoteCursorsIterator.close();
- }
- }
-
- long scan(final int maxMessagesToScan,
- final int maxMessagesToUpdate, final ArrayList<SmsMessage> smsToAdd,
- final LongSparseArray<MmsMessage> mmsToAdd,
- final ArrayList<LocalDatabaseMessage> messagesToDelete,
- final SyncManager.ThreadInfoCache threadInfoCache) {
- // Set of local messages matched with the timestamp of a remote message
- final Set<DatabaseMessage> matchedLocalMessages = Sets.newHashSet();
- // Set of remote messages matched with the timestamp of a local message
- final Set<DatabaseMessage> matchedRemoteMessages = Sets.newHashSet();
- long lastTimestampMillis = SYNC_STARTING;
- // Number of messages scanned local and remote
- int localCount = 0;
- int remoteCount = 0;
- // Seed the initial values of remote and local messages for comparison
- DatabaseMessage remoteMessage = mRemoteCursorsIterator.next();
- DatabaseMessage localMessage = mLocalCursorIterator.next();
- // Iterate through messages on both sides in reverse time order
- // Import messages in remote not in local, delete messages in local not in remote
- while (localCount + remoteCount < maxMessagesToScan && smsToAdd.size()
- + mmsToAdd.size() + messagesToDelete.size() < maxMessagesToUpdate) {
- if (remoteMessage == null && localMessage == null) {
- // No more message on both sides - scan complete
- lastTimestampMillis = SYNC_COMPLETE;
- break;
- } else if ((remoteMessage == null && localMessage != null) ||
- (localMessage != null && remoteMessage != null &&
- localMessage.getTimestampInMillis()
- > remoteMessage.getTimestampInMillis())) {
- // Found a local message that is not in remote db
- // Delete the local message
- messagesToDelete.add((LocalDatabaseMessage) localMessage);
- lastTimestampMillis = Math.min(lastTimestampMillis,
- localMessage.getTimestampInMillis());
- // Advance to next local message
- localMessage = mLocalCursorIterator.next();
- localCount += 1;
- } else if ((localMessage == null && remoteMessage != null) ||
- (localMessage != null && remoteMessage != null &&
- localMessage.getTimestampInMillis()
- < remoteMessage.getTimestampInMillis())) {
- // Found a remote message that is not in local db
- // Add the remote message
- saveMessageToAdd(smsToAdd, mmsToAdd, remoteMessage, threadInfoCache);
- lastTimestampMillis = Math.min(lastTimestampMillis,
- remoteMessage.getTimestampInMillis());
- // Advance to next remote message
- remoteMessage = mRemoteCursorsIterator.next();
- remoteCount += 1;
- } else {
- // Found remote and local messages at the same timestamp
- final long matchedTimestamp = localMessage.getTimestampInMillis();
- lastTimestampMillis = Math.min(lastTimestampMillis, matchedTimestamp);
- // Get the next local and remote messages
- final DatabaseMessage remoteMessagePeek = mRemoteCursorsIterator.next();
- final DatabaseMessage localMessagePeek = mLocalCursorIterator.next();
- // Check if only one message on each side matches the current timestamp
- // by looking at the next messages on both sides. If they are either null
- // (meaning no more messages) or having a different timestamp. We want
- // to optimize for this since this is the most common case when majority
- // of the messages are in sync (so they one-to-one pair up at each timestamp),
- // by not allocating the data structures required to compare a set of
- // messages from both sides.
- if ((remoteMessagePeek == null ||
- remoteMessagePeek.getTimestampInMillis() != matchedTimestamp) &&
- (localMessagePeek == null ||
- localMessagePeek.getTimestampInMillis() != matchedTimestamp)) {
- // Optimize the common case where only one message on each side
- // that matches the same timestamp
- if (!remoteMessage.equals(localMessage)) {
- // local != remote
- // Delete local message
- messagesToDelete.add((LocalDatabaseMessage) localMessage);
- // Add remote message
- saveMessageToAdd(smsToAdd, mmsToAdd, remoteMessage, threadInfoCache);
- }
- // Get next local and remote messages
- localMessage = localMessagePeek;
- remoteMessage = remoteMessagePeek;
- localCount += 1;
- remoteCount += 1;
- } else {
- // Rare case in which multiple messages are in the same timestamp
- // on either or both sides
- // Gather all the matched remote messages
- matchedRemoteMessages.clear();
- matchedRemoteMessages.add(remoteMessage);
- remoteCount += 1;
- remoteMessage = remoteMessagePeek;
- while (remoteMessage != null &&
- remoteMessage.getTimestampInMillis() == matchedTimestamp) {
- Assert.isTrue(!matchedRemoteMessages.contains(remoteMessage));
- matchedRemoteMessages.add(remoteMessage);
- remoteCount += 1;
- remoteMessage = mRemoteCursorsIterator.next();
- }
- // Gather all the matched local messages
- matchedLocalMessages.clear();
- matchedLocalMessages.add(localMessage);
- localCount += 1;
- localMessage = localMessagePeek;
- while (localMessage != null &&
- localMessage.getTimestampInMillis() == matchedTimestamp) {
- if (matchedLocalMessages.contains(localMessage)) {
- // Duplicate message is local database is deleted
- messagesToDelete.add((LocalDatabaseMessage) localMessage);
- } else {
- matchedLocalMessages.add(localMessage);
- }
- localCount += 1;
- localMessage = mLocalCursorIterator.next();
- }
- // Delete messages local only
- for (final DatabaseMessage msg : Sets.difference(
- matchedLocalMessages, matchedRemoteMessages)) {
- messagesToDelete.add((LocalDatabaseMessage) msg);
- }
- // Add messages remote only
- for (final DatabaseMessage msg : Sets.difference(
- matchedRemoteMessages, matchedLocalMessages)) {
- saveMessageToAdd(smsToAdd, mmsToAdd, msg, threadInfoCache);
- }
- }
- }
- }
- return lastTimestampMillis;
- }
-
- DatabaseMessage getLocalMessage() {
- return mLocalCursorIterator.next();
- }
-
- DatabaseMessage getRemoteMessage() {
- return mRemoteCursorsIterator.next();
- }
-
- int getLocalPosition() {
- return mLocalCursorIterator.getPosition();
- }
-
- int getRemotePosition() {
- return mRemoteCursorsIterator.getPosition();
- }
-
- int getLocalCount() {
- return mLocalCursorIterator.getCount();
- }
-
- int getRemoteCount() {
- return mRemoteCursorsIterator.getCount();
- }
-
- /**
- * An iterator for a database cursor
- */
- interface CursorIterator {
- /**
- * Move to next element in the cursor
- *
- * @return The next element (which becomes the current)
- */
- public DatabaseMessage next();
- /**
- * Close the cursor
- */
- public void close();
- /**
- * Get the position
- */
- public int getPosition();
- /**
- * Get the count
- */
- public int getCount();
- }
-
- private static final String ORDER_BY_DATE_DESC = "date DESC";
-
- // A subquery that selects SMS/MMS messages in Bugle which are also in telephony
- private static final String LOCAL_MESSAGES_SELECTION = String.format(
- Locale.US,
- "(%s NOTNULL)",
- MessageColumns.SMS_MESSAGE_URI);
-
- private static final String ORDER_BY_TIMESTAMP_DESC =
- MessageColumns.RECEIVED_TIMESTAMP + " DESC";
-
- // TODO : This should move into the provider
- private static class LocalMessageQuery {
- private static final String[] PROJECTION = new String[] {
- MessageColumns._ID,
- MessageColumns.RECEIVED_TIMESTAMP,
- MessageColumns.SMS_MESSAGE_URI,
- MessageColumns.PROTOCOL,
- MessageColumns.CONVERSATION_ID,
- };
- private static final int INDEX_MESSAGE_ID = 0;
- private static final int INDEX_MESSAGE_TIMESTAMP = 1;
- private static final int INDEX_SMS_MESSAGE_URI = 2;
- private static final int INDEX_MESSAGE_SMS_TYPE = 3;
- private static final int INDEX_CONVERSATION_ID = 4;
- }
-
- /**
- * This class provides the same DatabaseMessage interface over a local SMS db message
- */
- private static LocalDatabaseMessage getLocalDatabaseMessage(final Cursor cursor) {
- if (cursor == null) {
- return null;
- }
- return new LocalDatabaseMessage(
- cursor.getLong(LocalMessageQuery.INDEX_MESSAGE_ID),
- cursor.getInt(LocalMessageQuery.INDEX_MESSAGE_SMS_TYPE),
- cursor.getString(LocalMessageQuery.INDEX_SMS_MESSAGE_URI),
- cursor.getLong(LocalMessageQuery.INDEX_MESSAGE_TIMESTAMP),
- cursor.getString(LocalMessageQuery.INDEX_CONVERSATION_ID));
- }
-
- /**
- * The buffered cursor iterator for local SMS
- */
- private static class LocalCursorIterator implements CursorIterator {
- private Cursor mCursor;
- private final DatabaseWrapper mDatabase;
-
- LocalCursorIterator(final DatabaseWrapper database, final String selection)
- throws SQLiteException {
- mDatabase = database;
- try {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncCursorPair: Querying for local messages; selection = "
- + selection);
- }
- mCursor = mDatabase.query(
- DatabaseHelper.MESSAGES_TABLE,
- LocalMessageQuery.PROJECTION,
- selection,
- null /*selectionArgs*/,
- null/*groupBy*/,
- null/*having*/,
- ORDER_BY_TIMESTAMP_DESC);
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "SyncCursorPair: failed to query local sms/mms", e);
- // Can't query local database. So let's throw up the exception and abort sync
- // because we may end up import duplicate messages.
- throw e;
- }
- }
-
- @Override
- public DatabaseMessage next() {
- if (mCursor != null && mCursor.moveToNext()) {
- return getLocalDatabaseMessage(mCursor);
- }
- return null;
- }
-
- @Override
- public int getCount() {
- return (mCursor == null ? 0 : mCursor.getCount());
- }
-
- @Override
- public int getPosition() {
- return (mCursor == null ? 0 : mCursor.getPosition());
- }
-
- @Override
- public void close() {
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- }
- }
-
- /**
- * The cursor iterator for remote sms.
- * Since SMS and MMS are stored in different tables in telephony provider,
- * this class merges the two cursors and provides a unified view of messages
- * from both cursors. Note that the order is DESC.
- */
- private static class RemoteCursorsIterator implements CursorIterator {
- private Cursor mSmsCursor;
- private Cursor mMmsCursor;
- private DatabaseMessage mNextSms;
- private DatabaseMessage mNextMms;
-
- RemoteCursorsIterator(final String smsSelection, final String mmsSelection)
- throws SQLiteException {
- mSmsCursor = null;
- mMmsCursor = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncCursorPair: Querying for remote SMS; selection = "
- + smsSelection);
- }
- mSmsCursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Sms.CONTENT_URI,
- SmsMessage.getProjection(),
- smsSelection,
- null /* selectionArgs */,
- ORDER_BY_DATE_DESC);
- if (mSmsCursor == null) {
- LogUtil.w(TAG, "SyncCursorPair: Remote SMS query returned null cursor; "
- + "need to cancel sync");
- throw new RuntimeException("Null cursor from remote SMS query");
- }
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncCursorPair: Querying for remote MMS; selection = "
- + mmsSelection);
- }
- mMmsCursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Mms.CONTENT_URI,
- DatabaseMessages.MmsMessage.getProjection(),
- mmsSelection,
- null /* selectionArgs */,
- ORDER_BY_DATE_DESC);
- if (mMmsCursor == null) {
- LogUtil.w(TAG, "SyncCursorPair: Remote MMS query returned null cursor; "
- + "need to cancel sync");
- throw new RuntimeException("Null cursor from remote MMS query");
- }
- // Move to the first element in the combined stream from both cursors
- mNextSms = getSmsCursorNext();
- mNextMms = getMmsCursorNext();
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "SyncCursorPair: failed to query remote messages", e);
- // If we ignore this, the following code would think there is no remote message
- // and will delete all the local sms. We should be cautious here. So instead,
- // let's throw the exception to the caller and abort sms sync. We do the same
- // thing if either of the remote cursors is null.
- throw e;
- }
- }
-
- @Override
- public DatabaseMessage next() {
- DatabaseMessage result = null;
- if (mNextSms != null && mNextMms != null) {
- if (mNextSms.getTimestampInMillis() >= mNextMms.getTimestampInMillis()) {
- result = mNextSms;
- mNextSms = getSmsCursorNext();
- } else {
- result = mNextMms;
- mNextMms = getMmsCursorNext();
- }
- } else {
- if (mNextSms != null) {
- result = mNextSms;
- mNextSms = getSmsCursorNext();
- } else {
- result = mNextMms;
- mNextMms = getMmsCursorNext();
- }
- }
- return result;
- }
-
- private DatabaseMessage getSmsCursorNext() {
- if (mSmsCursor != null && mSmsCursor.moveToNext()) {
- return SmsMessage.get(mSmsCursor);
- }
- return null;
- }
-
- private DatabaseMessage getMmsCursorNext() {
- if (mMmsCursor != null && mMmsCursor.moveToNext()) {
- return MmsMessage.get(mMmsCursor);
- }
- return null;
- }
-
- @Override
- // Return approximate cursor position allowing for read ahead on two cursors (hence -1)
- public int getPosition() {
- return (mSmsCursor == null ? 0 : mSmsCursor.getPosition()) +
- (mMmsCursor == null ? 0 : mMmsCursor.getPosition()) - 1;
- }
-
- @Override
- public int getCount() {
- return (mSmsCursor == null ? 0 : mSmsCursor.getCount()) +
- (mMmsCursor == null ? 0 : mMmsCursor.getCount());
- }
-
- @Override
- public void close() {
- if (mSmsCursor != null) {
- mSmsCursor.close();
- mSmsCursor = null;
- }
- if (mMmsCursor != null) {
- mMmsCursor.close();
- mMmsCursor = null;
- }
- }
- }
-
- /**
- * Type selection for importing sms messages. Only SENT and INBOX messages are imported.
- *
- * @return The SQL selection for importing sms messages
- */
- public static String getSmsTypeSelectionSql() {
- return MmsUtils.getSmsTypeSelectionSql();
- }
-
- /**
- * Type selection for importing mms messages.
- *
- * Criteria:
- * MESSAGE_BOX is INBOX, SENT or OUTBOX
- * MESSAGE_TYPE is SEND_REQ (sent), RETRIEVE_CONF (received) or NOTIFICATION_IND (download)
- *
- * @return The SQL selection for importing mms messages. This selects the message type,
- * not including the selection on timestamp.
- */
- public static String getMmsTypeSelectionSql() {
- return MmsUtils.getMmsTypeSelectionSql();
- }
-
- /**
- * Get a SQL selection string using an existing selection and time window limits
- * The limits are not applied if the value is < 0
- *
- * @param typeSelection The existing selection
- * @param from The inclusive lower bound
- * @param to The exclusive upper bound
- * @return The created SQL selection
- */
- private static String getTimeConstrainedQuery(final String typeSelection,
- final String timeColumn, final long from, final long to,
- final String threadColumn, final String threadId) {
- final StringBuilder queryBuilder = new StringBuilder();
- queryBuilder.append(typeSelection);
- if (from > 0) {
- queryBuilder.append(" AND ").append(timeColumn).append(">=").append(from);
- }
- if (to > 0) {
- queryBuilder.append(" AND ").append(timeColumn).append("<").append(to);
- }
- if (!TextUtils.isEmpty(threadColumn) && !TextUtils.isEmpty(threadId)) {
- queryBuilder.append(" AND ").append(threadColumn).append("=").append(threadId);
- }
- return queryBuilder.toString();
- }
-
- private static final String[] COUNT_PROJECTION = new String[] { "count()" };
-
- private static int getCountFromCursor(final Cursor cursor) {
- if (cursor != null && cursor.moveToFirst()) {
- return cursor.getInt(0);
- }
- // We should only return a number if we were able to read it from the cursor.
- // Otherwise, we throw an exception to cancel the sync.
- String cursorDesc = "";
- if (cursor == null) {
- cursorDesc = "null";
- } else if (cursor.getCount() == 0) {
- cursorDesc = "empty";
- }
- throw new IllegalArgumentException("Cannot get count from " + cursorDesc + " cursor");
- }
-
- private void saveMessageToAdd(final List<SmsMessage> smsToAdd,
- final LongSparseArray<MmsMessage> mmsToAdd, final DatabaseMessage message,
- final ThreadInfoCache threadInfoCache) {
- long threadId;
- if (message.getProtocol() == MessageData.PROTOCOL_MMS) {
- final MmsMessage mms = (MmsMessage) message;
- mmsToAdd.append(mms.getId(), mms);
- threadId = mms.mThreadId;
- } else {
- final SmsMessage sms = (SmsMessage) message;
- smsToAdd.add(sms);
- threadId = sms.mThreadId;
- }
- // Cache the lookup and canonicalization of the phone number outside of the transaction...
- threadInfoCache.getThreadRecipients(threadId);
- }
-
- /**
- * Check if SMS has been synchronized. We compare the counts of messages on both
- * sides and return true if they are equal.
- *
- * Note that this may not be the most reliable way to tell if messages are in sync.
- * For example, the local misses one message and has one obsolete message.
- * However, we have background sms sync once a while, also some other events might
- * trigger a full sync. So we will eventually catch up. And this should be rare to
- * happen.
- *
- * @return If sms is in sync with telephony sms/mms providers
- */
- private static boolean isSynchronized(final DatabaseWrapper db, final String localSelection,
- final String[] localSelectionArgs, final String smsSelection,
- final String[] smsSelectionArgs, final String mmsSelection,
- final String[] mmsSelectionArgs) {
- final Context context = Factory.get().getApplicationContext();
- Cursor localCursor = null;
- Cursor remoteSmsCursor = null;
- Cursor remoteMmsCursor = null;
- try {
- localCursor = db.query(
- DatabaseHelper.MESSAGES_TABLE,
- COUNT_PROJECTION,
- localSelection,
- localSelectionArgs,
- null/*groupBy*/,
- null/*having*/,
- null/*orderBy*/);
- final int localCount = getCountFromCursor(localCursor);
- remoteSmsCursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Sms.CONTENT_URI,
- COUNT_PROJECTION,
- smsSelection,
- smsSelectionArgs,
- null/*orderBy*/);
- final int smsCount = getCountFromCursor(remoteSmsCursor);
- remoteMmsCursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Mms.CONTENT_URI,
- COUNT_PROJECTION,
- mmsSelection,
- mmsSelectionArgs,
- null/*orderBy*/);
- final int mmsCount = getCountFromCursor(remoteMmsCursor);
- final int remoteCount = smsCount + mmsCount;
- final boolean isInSync = (localCount == remoteCount);
- if (isInSync) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncCursorPair: Same # of local and remote messages = "
- + localCount);
- }
- } else {
- LogUtil.i(TAG, "SyncCursorPair: Not in sync; # local messages = " + localCount
- + ", # remote message = " + remoteCount);
- }
- return isInSync;
- } catch (final Exception e) {
- LogUtil.e(TAG, "SyncCursorPair: failed to query local or remote message counts", e);
- // If something is wrong in querying database, assume we are synced so
- // we don't retry indefinitely
- } finally {
- if (localCursor != null) {
- localCursor.close();
- }
- if (remoteSmsCursor != null) {
- remoteSmsCursor.close();
- }
- if (remoteMmsCursor != null) {
- remoteMmsCursor.close();
- }
- }
- return true;
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/SyncMessageBatch.java b/src/com/android/messaging/datamodel/action/SyncMessageBatch.java
deleted file mode 100644
index 972d691..0000000
--- a/src/com/android/messaging/datamodel/action/SyncMessageBatch.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteConstraintException;
-import android.provider.Telephony;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.SyncManager.ThreadInfoCache;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage;
-import com.android.messaging.sms.DatabaseMessages.MmsMessage;
-import com.android.messaging.sms.DatabaseMessages.SmsMessage;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Update local database with a batch of messages to add/delete in one transaction
- */
-class SyncMessageBatch {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- // Variables used during executeAction
- private final HashSet<String> mConversationsToUpdate;
- // Cache of thread->conversationId map
- private final ThreadInfoCache mCache;
-
- // Set of SMS messages to add
- private final ArrayList<SmsMessage> mSmsToAdd;
- // Set of MMS messages to add
- private final ArrayList<MmsMessage> mMmsToAdd;
- // Set of local messages to delete
- private final ArrayList<LocalDatabaseMessage> mMessagesToDelete;
-
- SyncMessageBatch(final ArrayList<SmsMessage> smsToAdd,
- final ArrayList<MmsMessage> mmsToAdd,
- final ArrayList<LocalDatabaseMessage> messagesToDelete,
- final ThreadInfoCache cache) {
- mSmsToAdd = smsToAdd;
- mMmsToAdd = mmsToAdd;
- mMessagesToDelete = messagesToDelete;
- mCache = cache;
- mConversationsToUpdate = new HashSet<String>();
- }
-
- void updateLocalDatabase() {
- // Perform local database changes in one transaction
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- try {
- // Store all the SMS messages
- for (final SmsMessage sms : mSmsToAdd) {
- storeSms(db, sms);
- }
- // Store all the MMS messages
- for (final MmsMessage mms : mMmsToAdd) {
- storeMms(db, mms);
- }
- // Keep track of conversations with messages deleted
- for (final LocalDatabaseMessage message : mMessagesToDelete) {
- mConversationsToUpdate.add(message.getConversationId());
- }
- // Batch delete local messages
- batchDelete(db, DatabaseHelper.MESSAGES_TABLE, MessageColumns._ID,
- messageListToIds(mMessagesToDelete));
-
- for (final LocalDatabaseMessage message : mMessagesToDelete) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncMessageBatch: Deleted message " + message.getLocalId()
- + " for SMS/MMS " + message.getUri() + " with timestamp "
- + message.getTimestampInMillis());
- }
- }
-
- // Update conversation state for imported messages, like snippet,
- updateConversations(db);
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- private static String[] messageListToIds(final List<LocalDatabaseMessage> messagesToDelete) {
- final String[] ids = new String[messagesToDelete.size()];
- for (int i = 0; i < ids.length; i++) {
- ids[i] = Long.toString(messagesToDelete.get(i).getLocalId());
- }
- return ids;
- }
-
- /**
- * Store the SMS message into local database.
- *
- * @param sms
- */
- private void storeSms(final DatabaseWrapper db, final SmsMessage sms) {
- if (sms.mBody == null) {
- LogUtil.w(TAG, "SyncMessageBatch: SMS " + sms.mUri + " has no body; adding empty one");
- // try to fix it
- sms.mBody = "";
- }
-
- if (TextUtils.isEmpty(sms.mAddress)) {
- LogUtil.e(TAG, "SyncMessageBatch: SMS has no address; using unknown sender");
- // try to fix it
- sms.mAddress = ParticipantData.getUnknownSenderDestination();
- }
-
- // TODO : We need to also deal with messages in a failed/retry state
- final boolean isOutgoing = sms.mType != Sms.MESSAGE_TYPE_INBOX;
-
- final String otherPhoneNumber = sms.mAddress;
-
- // A forced resync of all messages should still keep the archived states.
- // The database upgrade code notifies sync manager of this. We need to
- // honor the original customization to this conversation if created.
- final String conversationId = mCache.getOrCreateConversation(db, sms.mThreadId, sms.mSubId,
- DataModel.get().getSyncManager().getCustomizationForThread(sms.mThreadId));
- if (conversationId == null) {
- // Cannot create conversation for this message? This should not happen.
- LogUtil.e(TAG, "SyncMessageBatch: Failed to create conversation for SMS thread "
- + sms.mThreadId);
- return;
- }
- final ParticipantData self = ParticipantData.getSelfParticipant(sms.getSubId());
- final String selfId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
- final ParticipantData sender = isOutgoing ?
- self :
- ParticipantData.getFromRawPhoneBySimLocale(otherPhoneNumber, sms.getSubId());
- final String participantId = (isOutgoing ? selfId :
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender));
-
- final int bugleStatus = bugleStatusForSms(isOutgoing, sms.mType, sms.mStatus);
-
- final MessageData message = MessageData.createSmsMessage(
- sms.mUri,
- participantId,
- selfId,
- conversationId,
- bugleStatus,
- sms.mSeen,
- sms.mRead,
- sms.mTimestampSentInMillis,
- sms.mTimestampInMillis,
- sms.mBody);
-
- // Inserting sms content into messages table
- try {
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
- } catch (SQLiteConstraintException e) {
- rethrowSQLiteConstraintExceptionWithDetails(e, db, sms.mUri, sms.mThreadId,
- conversationId, selfId, participantId);
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncMessageBatch: Inserted new message " + message.getMessageId()
- + " for SMS " + message.getSmsMessageUri() + " received at "
- + message.getReceivedTimeStamp());
- }
-
- // Keep track of updated conversation for later updating the conversation snippet, etc.
- mConversationsToUpdate.add(conversationId);
- }
-
- public static int bugleStatusForSms(final boolean isOutgoing, final int type,
- final int status) {
- int bugleStatus = MessageData.BUGLE_STATUS_UNKNOWN;
- // For a message we sync either
- if (isOutgoing) {
- // Outgoing message not yet been sent
- if (type == Telephony.Sms.MESSAGE_TYPE_FAILED ||
- type == Telephony.Sms.MESSAGE_TYPE_OUTBOX ||
- type == Telephony.Sms.MESSAGE_TYPE_QUEUED ||
- (type == Telephony.Sms.MESSAGE_TYPE_SENT &&
- status == Telephony.Sms.STATUS_FAILED)) {
- // Not sent counts as failed and available for manual resend
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_FAILED;
- } else if (status == Sms.STATUS_COMPLETE) {
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_DELIVERED;
- } else {
- // Otherwise outgoing message is complete
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
- }
- } else {
- // All incoming SMS messages are complete
- bugleStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE;
- }
- return bugleStatus;
- }
-
- /**
- * Store the MMS message into local database
- *
- * @param mms
- */
- private void storeMms(final DatabaseWrapper db, final MmsMessage mms) {
- if (mms.mParts.size() < 1) {
- LogUtil.w(TAG, "SyncMessageBatch: MMS " + mms.mUri + " has no parts");
- }
-
- // TODO : We need to also deal with messages in a failed/retry state
- final boolean isOutgoing = mms.mType != Mms.MESSAGE_BOX_INBOX;
- final boolean isNotification = (mms.mMmsMessageType ==
- PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
-
- final String senderId = mms.mSender;
-
- // A forced resync of all messages should still keep the archived states.
- // The database upgrade code notifies sync manager of this. We need to
- // honor the original customization to this conversation if created.
- final String conversationId = mCache.getOrCreateConversation(db, mms.mThreadId, mms.mSubId,
- DataModel.get().getSyncManager().getCustomizationForThread(mms.mThreadId));
- if (conversationId == null) {
- LogUtil.e(TAG, "SyncMessageBatch: Failed to create conversation for MMS thread "
- + mms.mThreadId);
- return;
- }
- final ParticipantData self = ParticipantData.getSelfParticipant(mms.getSubId());
- final String selfId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
- final ParticipantData sender = isOutgoing ?
- self : ParticipantData.getFromRawPhoneBySimLocale(senderId, mms.getSubId());
- final String participantId = (isOutgoing ? selfId :
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender));
-
- final int bugleStatus = MmsUtils.bugleStatusForMms(isOutgoing, isNotification, mms.mType);
-
- // Import message and all of the parts.
- // TODO : For now we are importing these in the order we found them in the MMS
- // database. Ideally we would load and parse the SMIL which describes how the parts relate
- // to one another.
-
- // TODO: Need to set correct status on message
- final MessageData message = MmsUtils.createMmsMessage(mms, conversationId, participantId,
- selfId, bugleStatus);
-
- // Inserting mms content into messages table
- try {
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
- } catch (SQLiteConstraintException e) {
- rethrowSQLiteConstraintExceptionWithDetails(e, db, mms.mUri, mms.mThreadId,
- conversationId, selfId, participantId);
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncMessageBatch: Inserted new message " + message.getMessageId()
- + " for MMS " + message.getSmsMessageUri() + " received at "
- + message.getReceivedTimeStamp());
- }
-
- // Keep track of updated conversation for later updating the conversation snippet, etc.
- mConversationsToUpdate.add(conversationId);
- }
-
- // TODO: Remove this after we no longer see this crash (b/18375758)
- private static void rethrowSQLiteConstraintExceptionWithDetails(SQLiteConstraintException e,
- DatabaseWrapper db, String messageUri, long threadId, String conversationId,
- String selfId, String senderId) {
- // Add some extra debug information to the exception for tracking down b/18375758.
- // The default detail message for SQLiteConstraintException tells us that a foreign
- // key constraint failed, but not which one! Messages have foreign keys to 3 tables:
- // conversations, participants (self), participants (sender). We'll query each one
- // to determine which one(s) violated the constraint, and then throw a new exception
- // with those details.
-
- String foundConversationId = null;
- Cursor cursor = null;
- try {
- // Look for an existing conversation in the db with the conversation id
- cursor = db.rawQuery("SELECT " + ConversationColumns._ID
- + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
- + " WHERE " + ConversationColumns._ID + "=" + conversationId,
- null);
- if (cursor != null && cursor.moveToFirst()) {
- Assert.isTrue(cursor.getCount() == 1);
- foundConversationId = cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- ParticipantData foundSelfParticipant =
- BugleDatabaseOperations.getExistingParticipant(db, selfId);
- ParticipantData foundSenderParticipant =
- BugleDatabaseOperations.getExistingParticipant(db, senderId);
-
- String errorMsg = "SQLiteConstraintException while inserting message for " + messageUri
- + "; conversation id from getOrCreateConversation = " + conversationId
- + " (lookup thread = " + threadId + "), found conversation id = "
- + foundConversationId + ", found self participant = "
- + LogUtil.sanitizePII(foundSelfParticipant.getNormalizedDestination())
- + " (lookup id = " + selfId + "), found sender participant = "
- + LogUtil.sanitizePII(foundSenderParticipant.getNormalizedDestination())
- + " (lookup id = " + senderId + ")";
- throw new RuntimeException(errorMsg, e);
- }
-
- /**
- * Use the tracked latest message info to update conversations, including
- * latest chat message and sort timestamp.
- */
- private void updateConversations(final DatabaseWrapper db) {
- for (final String conversationId : mConversationsToUpdate) {
- if (BugleDatabaseOperations.deleteConversationIfEmptyInTransaction(db,
- conversationId)) {
- continue;
- }
-
- final boolean archived = mCache.isArchived(conversationId);
- // Always attempt to auto-switch conversation self id for sync/import case.
- BugleDatabaseOperations.maybeRefreshConversationMetadataInTransaction(db,
- conversationId, true /*shouldAutoSwitchSelfId*/, archived /*keepArchived*/);
- }
- }
-
-
- /**
- * Batch delete database rows by matching a column with a list of values, usually some
- * kind of IDs.
- *
- * @param table
- * @param column
- * @param ids
- * @return Total number of deleted messages
- */
- private static int batchDelete(final DatabaseWrapper db, final String table,
- final String column, final String[] ids) {
- int totalDeleted = 0;
- final int totalIds = ids.length;
- for (int start = 0; start < totalIds; start += MmsUtils.MAX_IDS_PER_QUERY) {
- final int end = Math.min(start + MmsUtils.MAX_IDS_PER_QUERY, totalIds); //excluding
- final int count = end - start;
- final String batchSelection = String.format(
- Locale.US,
- "%s IN %s",
- column,
- MmsUtils.getSqlInOperand(count));
- final String[] batchSelectionArgs = Arrays.copyOfRange(ids, start, end);
- final int deleted = db.delete(
- table,
- batchSelection,
- batchSelectionArgs);
- totalDeleted += deleted;
- }
- return totalDeleted;
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/SyncMessagesAction.java b/src/com/android/messaging/datamodel/action/SyncMessagesAction.java
deleted file mode 100644
index f4a3e1f..0000000
--- a/src/com/android/messaging/datamodel/action/SyncMessagesAction.java
+++ /dev/null
@@ -1,637 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.provider.Telephony.Mms;
-import android.support.v4.util.LongSparseArray;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.SyncManager.ThreadInfoCache;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.sms.DatabaseMessages;
-import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage;
-import com.android.messaging.sms.DatabaseMessages.MmsMessage;
-import com.android.messaging.sms.DatabaseMessages.SmsMessage;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.BuglePrefsKeys;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Action used to sync messages from smsmms db to local database
- */
-public class SyncMessagesAction extends Action implements Parcelable {
- static final long SYNC_FAILED = Long.MIN_VALUE;
-
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- private static final String KEY_START_TIMESTAMP = "start_timestamp";
- private static final String KEY_MAX_UPDATE = "max_update";
- private static final String KEY_LOWER_BOUND = "lower_bound";
- private static final String KEY_UPPER_BOUND = "upper_bound";
- private static final String BUNDLE_KEY_LAST_TIMESTAMP = "last_timestamp";
- private static final String BUNDLE_KEY_SMS_MESSAGES = "sms_to_add";
- private static final String BUNDLE_KEY_MMS_MESSAGES = "mms_to_add";
- private static final String BUNDLE_KEY_MESSAGES_TO_DELETE = "messages_to_delete";
-
- /**
- * Start a full sync (backed off a few seconds to avoid pulling sending/receiving messages).
- */
- public static void fullSync() {
- final BugleGservices bugleGservices = BugleGservices.get();
- final long smsSyncBackoffTimeMillis = bugleGservices.getLong(
- BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS,
- BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT);
-
- final long now = System.currentTimeMillis();
- // TODO: Could base this off most recent message in db but now should be okay...
- final long startTimestamp = now - smsSyncBackoffTimeMillis;
-
- final SyncMessagesAction action = new SyncMessagesAction(-1L, startTimestamp,
- 0, startTimestamp);
- action.start();
- }
-
- /**
- * Start an incremental sync to pull messages since last sync (backed off a few seconds)..
- */
- public static void sync() {
- final BugleGservices bugleGservices = BugleGservices.get();
- final long smsSyncBackoffTimeMillis = bugleGservices.getLong(
- BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS,
- BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT);
-
- final long now = System.currentTimeMillis();
- // TODO: Could base this off most recent message in db but now should be okay...
- final long startTimestamp = now - smsSyncBackoffTimeMillis;
-
- sync(startTimestamp);
- }
-
- /**
- * Start an incremental sync when the application starts up (no back off as not yet
- * sending/receiving).
- */
- public static void immediateSync() {
- final long now = System.currentTimeMillis();
- // TODO: Could base this off most recent message in db but now should be okay...
- final long startTimestamp = now;
-
- sync(startTimestamp);
- }
-
- private static void sync(final long startTimestamp) {
- if (!OsUtil.hasSmsPermission()) {
- // Sync requires READ_SMS permission
- return;
- }
-
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- // Lower bound is end of previous sync
- final long syncLowerBoundTimeMillis = prefs.getLong(BuglePrefsKeys.LAST_SYNC_TIME,
- BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT);
-
- final SyncMessagesAction action = new SyncMessagesAction(syncLowerBoundTimeMillis,
- startTimestamp, 0, startTimestamp);
- action.start();
- }
-
- private SyncMessagesAction(final long lowerBound, final long upperBound,
- final int maxMessagesToUpdate, final long startTimestamp) {
- actionParameters.putLong(KEY_LOWER_BOUND, lowerBound);
- actionParameters.putLong(KEY_UPPER_BOUND, upperBound);
- actionParameters.putInt(KEY_MAX_UPDATE, maxMessagesToUpdate);
- actionParameters.putLong(KEY_START_TIMESTAMP, startTimestamp);
- }
-
- @Override
- protected Object executeAction() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND);
- final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND);
- final int initialMaxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE);
- final long startTimestamp = actionParameters.getLong(KEY_START_TIMESTAMP);
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: Request to sync messages from "
- + lowerBoundTimeMillis + " to " + upperBoundTimeMillis + " (start timestamp = "
- + startTimestamp + ", message update limit = " + initialMaxMessagesToUpdate
- + ")");
- }
-
- final SyncManager syncManager = DataModel.get().getSyncManager();
- if (lowerBoundTimeMillis >= 0) {
- // Cursors
- final SyncCursorPair cursors = new SyncCursorPair(-1L, lowerBoundTimeMillis);
- final boolean inSync = cursors.isSynchronized(db);
- if (!inSync) {
- if (syncManager.delayUntilFullSync(startTimestamp) == 0) {
- lowerBoundTimeMillis = -1;
- actionParameters.putLong(KEY_LOWER_BOUND, lowerBoundTimeMillis);
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: Messages before "
- + lowerBoundTimeMillis + " not in sync; promoting to full sync");
- }
- } else if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: Messages before "
- + lowerBoundTimeMillis + " not in sync; will do incremental sync");
- }
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: Messages before " + lowerBoundTimeMillis
- + " are in sync");
- }
- }
- }
-
- // Check if sync allowed (can be too soon after last or one is already running)
- if (syncManager.shouldSync(lowerBoundTimeMillis < 0, startTimestamp)) {
- syncManager.startSyncBatch(upperBoundTimeMillis);
- requestBackgroundWork();
- }
-
- return null;
- }
-
- @Override
- protected Bundle doBackgroundWork() {
- final BugleGservices bugleGservices = BugleGservices.get();
- final DatabaseWrapper db = DataModel.get().getDatabase();
-
- final int maxMessagesToScan = bugleGservices.getInt(
- BugleGservicesKeys.SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN,
- BugleGservicesKeys.SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN_DEFAULT);
-
- final int initialMaxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE);
- final int smsSyncSubsequentBatchSizeMin = bugleGservices.getInt(
- BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MIN,
- BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MIN_DEFAULT);
- final int smsSyncSubsequentBatchSizeMax = bugleGservices.getInt(
- BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MAX,
- BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MAX_DEFAULT);
-
- // Cap sync size to GServices limits
- final int maxMessagesToUpdate = Math.max(smsSyncSubsequentBatchSizeMin,
- Math.min(initialMaxMessagesToUpdate, smsSyncSubsequentBatchSizeMax));
-
- final long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND);
- final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND);
-
- LogUtil.i(TAG, "SyncMessagesAction: Starting batch for messages from "
- + lowerBoundTimeMillis + " to " + upperBoundTimeMillis
- + " (message update limit = " + maxMessagesToUpdate + ", message scan limit = "
- + maxMessagesToScan + ")");
-
- // Clear last change time so that we can work out if this batch is dirty when it completes
- final SyncManager syncManager = DataModel.get().getSyncManager();
-
- // Clear the singleton cache that maps threads to recipients and to conversations.
- final SyncManager.ThreadInfoCache cache = syncManager.getThreadInfoCache();
- cache.clear();
-
- // Sms messages to store
- final ArrayList<SmsMessage> smsToAdd = new ArrayList<SmsMessage>();
- // Mms messages to store
- final LongSparseArray<MmsMessage> mmsToAdd = new LongSparseArray<MmsMessage>();
- // List of local SMS/MMS to remove
- final ArrayList<LocalDatabaseMessage> messagesToDelete =
- new ArrayList<LocalDatabaseMessage>();
-
- long lastTimestampMillis = SYNC_FAILED;
- if (syncManager.isSyncing(upperBoundTimeMillis)) {
- // Cursors
- final SyncCursorPair cursors = new SyncCursorPair(lowerBoundTimeMillis,
- upperBoundTimeMillis);
-
- // Actually compare the messages using cursor pair
- lastTimestampMillis = syncCursorPair(db, cursors, smsToAdd, mmsToAdd,
- messagesToDelete, maxMessagesToScan, maxMessagesToUpdate, cache);
- }
- final Bundle response = new Bundle();
-
- // If comparison succeeds bundle up the changes for processing in ActionService
- if (lastTimestampMillis > SYNC_FAILED) {
- final ArrayList<MmsMessage> mmsToAddList = new ArrayList<MmsMessage>();
- for (int i = 0; i < mmsToAdd.size(); i++) {
- final MmsMessage mms = mmsToAdd.valueAt(i);
- mmsToAddList.add(mms);
- }
-
- response.putParcelableArrayList(BUNDLE_KEY_SMS_MESSAGES, smsToAdd);
- response.putParcelableArrayList(BUNDLE_KEY_MMS_MESSAGES, mmsToAddList);
- response.putParcelableArrayList(BUNDLE_KEY_MESSAGES_TO_DELETE, messagesToDelete);
- }
- response.putLong(BUNDLE_KEY_LAST_TIMESTAMP, lastTimestampMillis);
-
- return response;
- }
-
- /**
- * Compare messages based on timestamp and uri
- * @param db local database wrapper
- * @param cursors cursor pair holding references to local and remote messages
- * @param smsToAdd newly found sms messages to add
- * @param mmsToAdd newly found mms messages to add
- * @param messagesToDelete messages not found needing deletion
- * @param maxMessagesToScan max messages to scan for changes
- * @param maxMessagesToUpdate max messages to return for updates
- * @param cache cache for conversation id / thread id / recipient set mapping
- * @return timestamp of the oldest message seen during the sync scan
- */
- private long syncCursorPair(final DatabaseWrapper db, final SyncCursorPair cursors,
- final ArrayList<SmsMessage> smsToAdd, final LongSparseArray<MmsMessage> mmsToAdd,
- final ArrayList<LocalDatabaseMessage> messagesToDelete, final int maxMessagesToScan,
- final int maxMessagesToUpdate, final ThreadInfoCache cache) {
- long lastTimestampMillis;
- final long startTimeMillis = SystemClock.elapsedRealtime();
-
- // Number of messages scanned local and remote
- int localPos = 0;
- int remotePos = 0;
- int localTotal = 0;
- int remoteTotal = 0;
- // Scan through the messages on both sides and prepare messages for local message table
- // changes (including adding and deleting)
- try {
- cursors.query(db);
-
- localTotal = cursors.getLocalCount();
- remoteTotal = cursors.getRemoteCount();
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: Scanning cursors (local count = " + localTotal
- + ", remote count = " + remoteTotal + ", message update limit = "
- + maxMessagesToUpdate + ", message scan limit = " + maxMessagesToScan
- + ")");
- }
-
- lastTimestampMillis = cursors.scan(maxMessagesToScan, maxMessagesToUpdate,
- smsToAdd, mmsToAdd, messagesToDelete, cache);
-
- localPos = cursors.getLocalPosition();
- remotePos = cursors.getRemotePosition();
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: Scanned cursors (local position = " + localPos
- + " of " + localTotal + ", remote position = " + remotePos + " of "
- + remoteTotal + ")");
- }
-
- // Batch loading the parts of the MMS messages in this batch
- loadMmsParts(mmsToAdd);
- // Lookup senders for incoming mms messages
- setMmsSenders(mmsToAdd, cache);
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "SyncMessagesAction: Database exception", e);
- // Let's abort
- lastTimestampMillis = SYNC_FAILED;
- } catch (final Exception e) {
- // We want to catch anything unexpected since this is running in a separate thread
- // and any unexpected exception will just fail this thread silently.
- // Let's crash for dogfooders!
- LogUtil.wtf(TAG, "SyncMessagesAction: unexpected failure in scan", e);
- lastTimestampMillis = SYNC_FAILED;
- } finally {
- if (cursors != null) {
- cursors.close();
- }
- }
-
- final long endTimeMillis = SystemClock.elapsedRealtime();
-
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: Scan complete (took "
- + (endTimeMillis - startTimeMillis) + " ms). " + smsToAdd.size()
- + " remote SMS to add, " + mmsToAdd.size() + " MMS to add, "
- + messagesToDelete.size() + " local messages to delete. "
- + "Oldest timestamp seen = " + lastTimestampMillis);
- }
-
- return lastTimestampMillis;
- }
-
- /**
- * Perform local database updates and schedule follow on sync actions
- */
- @Override
- protected Object processBackgroundResponse(final Bundle response) {
- final long lastTimestampMillis = response.getLong(BUNDLE_KEY_LAST_TIMESTAMP);
- final long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND);
- final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND);
- final int maxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE);
- final long startTimestamp = actionParameters.getLong(KEY_START_TIMESTAMP);
-
- // Check with the sync manager if any conflicting updates have been made to databases
- final SyncManager syncManager = DataModel.get().getSyncManager();
- final boolean orphan = !syncManager.isSyncing(upperBoundTimeMillis);
-
- // lastTimestampMillis used to indicate failure
- if (orphan) {
- // This batch does not match current in progress timestamp.
- LogUtil.w(TAG, "SyncMessagesAction: Ignoring orphan sync batch for messages from "
- + lowerBoundTimeMillis + " to " + upperBoundTimeMillis);
- } else {
- final boolean dirty = syncManager.isBatchDirty(lastTimestampMillis);
- if (lastTimestampMillis == SYNC_FAILED) {
- LogUtil.e(TAG, "SyncMessagesAction: Sync failed - terminating");
-
- // Failed - update last sync times to throttle our failure rate
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- // Save sync completion time so next sync will start from here
- prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, startTimestamp);
- // Remember last full sync so that don't start background full sync right away
- prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, startTimestamp);
-
- syncManager.complete();
- } else if (dirty) {
- LogUtil.w(TAG, "SyncMessagesAction: Redoing dirty sync batch of messages from "
- + lowerBoundTimeMillis + " to " + upperBoundTimeMillis);
-
- // Redo this batch
- final SyncMessagesAction nextBatch =
- new SyncMessagesAction(lowerBoundTimeMillis, upperBoundTimeMillis,
- maxMessagesToUpdate, startTimestamp);
-
- syncManager.startSyncBatch(upperBoundTimeMillis);
- requestBackgroundWork(nextBatch);
- } else {
- // Succeeded
- final ArrayList<SmsMessage> smsToAdd =
- response.getParcelableArrayList(BUNDLE_KEY_SMS_MESSAGES);
- final ArrayList<MmsMessage> mmsToAdd =
- response.getParcelableArrayList(BUNDLE_KEY_MMS_MESSAGES);
- final ArrayList<LocalDatabaseMessage> messagesToDelete =
- response.getParcelableArrayList(BUNDLE_KEY_MESSAGES_TO_DELETE);
-
- final int messagesUpdated = smsToAdd.size() + mmsToAdd.size()
- + messagesToDelete.size();
-
- // Perform local database changes in one transaction
- long txnTimeMillis = 0;
- if (messagesUpdated > 0) {
- final long startTimeMillis = SystemClock.elapsedRealtime();
- final SyncMessageBatch batch = new SyncMessageBatch(smsToAdd, mmsToAdd,
- messagesToDelete, syncManager.getThreadInfoCache());
- batch.updateLocalDatabase();
- final long endTimeMillis = SystemClock.elapsedRealtime();
- txnTimeMillis = endTimeMillis - startTimeMillis;
-
- LogUtil.i(TAG, "SyncMessagesAction: Updated local database "
- + "(took " + txnTimeMillis + " ms). Added "
- + smsToAdd.size() + " SMS, added " + mmsToAdd.size() + " MMS, deleted "
- + messagesToDelete.size() + " messages.");
-
- // TODO: Investigate whether we can make this more fine-grained.
- MessagingContentProvider.notifyEverythingChanged();
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: No local database updates to make");
- }
-
- if (!syncManager.getHasFirstSyncCompleted()) {
- // If we have never completed a sync before (fresh install) and there are
- // no messages, still inform the UI of a change so it can update syncing
- // messages shown to the user
- MessagingContentProvider.notifyConversationListChanged();
- MessagingContentProvider.notifyPartsChanged();
- }
- }
- // Determine if there are more messages that need to be scanned
- if (lastTimestampMillis >= 0 && lastTimestampMillis >= lowerBoundTimeMillis) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncMessagesAction: More messages to sync; scheduling next "
- + "sync batch now.");
- }
-
- // Include final millisecond of last sync in next sync
- final long newUpperBoundTimeMillis = lastTimestampMillis + 1;
- final int newMaxMessagesToUpdate = nextBatchSize(messagesUpdated,
- txnTimeMillis);
-
- final SyncMessagesAction nextBatch =
- new SyncMessagesAction(lowerBoundTimeMillis, newUpperBoundTimeMillis,
- newMaxMessagesToUpdate, startTimestamp);
-
- // Proceed with next batch
- syncManager.startSyncBatch(newUpperBoundTimeMillis);
- requestBackgroundWork(nextBatch);
- } else {
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- // Save sync completion time so next sync will start from here
- prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, startTimestamp);
- if (lowerBoundTimeMillis < 0) {
- // Remember last full sync so that don't start another full sync right away
- prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, startTimestamp);
- }
-
- final long now = System.currentTimeMillis();
-
- // After any sync check if new messages have arrived
- final SyncCursorPair recents = new SyncCursorPair(startTimestamp, now);
- final SyncCursorPair olders = new SyncCursorPair(-1L, startTimestamp);
- final DatabaseWrapper db = DataModel.get().getDatabase();
- if (!recents.isSynchronized(db)) {
- LogUtil.i(TAG, "SyncMessagesAction: Changed messages after sync; "
- + "scheduling an incremental sync now.");
-
- // Just add a new batch for recent messages
- final SyncMessagesAction nextBatch =
- new SyncMessagesAction(startTimestamp, now, 0, startTimestamp);
- syncManager.startSyncBatch(now);
- requestBackgroundWork(nextBatch);
- // After partial sync verify sync state
- } else if (lowerBoundTimeMillis >= 0 && !olders.isSynchronized(db)) {
- // Add a batch going back to start of time
- LogUtil.w(TAG, "SyncMessagesAction: Changed messages before sync batch; "
- + "scheduling a full sync now.");
-
- final SyncMessagesAction nextBatch =
- new SyncMessagesAction(-1L, startTimestamp, 0, startTimestamp);
-
- syncManager.startSyncBatch(startTimestamp);
- requestBackgroundWork(nextBatch);
- } else {
- LogUtil.i(TAG, "SyncMessagesAction: All messages now in sync");
-
- // All done, in sync
- syncManager.complete();
- }
- }
- // Either sync should be complete or we should have a follow up request
- Assert.isTrue(hasBackgroundActions() || !syncManager.isSyncing());
- }
- }
-
- return null;
- }
-
- /**
- * Decide the next batch size based on the stats we collected with past batch
- * @param messagesUpdated number of messages updated in this batch
- * @param txnTimeMillis time the transaction took in ms
- * @return Target number of messages to sync for next batch
- */
- private static int nextBatchSize(final int messagesUpdated, final long txnTimeMillis) {
- final BugleGservices bugleGservices = BugleGservices.get();
- final long smsSyncSubsequentBatchTimeLimitMillis = bugleGservices.getLong(
- BugleGservicesKeys.SMS_SYNC_BATCH_TIME_LIMIT_MILLIS,
- BugleGservicesKeys.SMS_SYNC_BATCH_TIME_LIMIT_MILLIS_DEFAULT);
-
- if (txnTimeMillis <= 0) {
- return 0;
- }
- // Number of messages we can sync within the batch time limit using
- // the average sync time calculated based on the stats we collected
- // in previous batch
- return (int) ((double) (messagesUpdated) / (double) txnTimeMillis
- * smsSyncSubsequentBatchTimeLimitMillis);
- }
-
- /**
- * Batch loading MMS parts for the messages in current batch
- */
- private void loadMmsParts(final LongSparseArray<MmsMessage> mmses) {
- final Context context = Factory.get().getApplicationContext();
- final int totalIds = mmses.size();
- for (int start = 0; start < totalIds; start += MmsUtils.MAX_IDS_PER_QUERY) {
- final int end = Math.min(start + MmsUtils.MAX_IDS_PER_QUERY, totalIds); //excluding
- final int count = end - start;
- final String batchSelection = String.format(
- Locale.US,
- "%s != '%s' AND %s IN %s",
- Mms.Part.CONTENT_TYPE,
- ContentType.APP_SMIL,
- Mms.Part.MSG_ID,
- MmsUtils.getSqlInOperand(count));
- final String[] batchSelectionArgs = new String[count];
- for (int i = 0; i < count; i++) {
- batchSelectionArgs[i] = Long.toString(mmses.valueAt(start + i).getId());
- }
- final Cursor cursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- MmsUtils.MMS_PART_CONTENT_URI,
- DatabaseMessages.MmsPart.PROJECTION,
- batchSelection,
- batchSelectionArgs,
- null/*sortOrder*/);
- if (cursor != null) {
- try {
- while (cursor.moveToNext()) {
- // Delay loading the media content for parsing for efficiency
- // TODO: load the media and fill in the dimensions when
- // we actually display it
- final DatabaseMessages.MmsPart part =
- DatabaseMessages.MmsPart.get(cursor, false/*loadMedia*/);
- final DatabaseMessages.MmsMessage mms = mmses.get(part.mMessageId);
- if (mms != null) {
- mms.addPart(part);
- }
- }
- } finally {
- cursor.close();
- }
- }
- }
- }
-
- /**
- * Batch loading MMS sender for the messages in current batch
- */
- private void setMmsSenders(final LongSparseArray<MmsMessage> mmses,
- final ThreadInfoCache cache) {
- // Store all the MMS messages
- for (int i = 0; i < mmses.size(); i++) {
- final MmsMessage mms = mmses.valueAt(i);
-
- final boolean isOutgoing = mms.mType != Mms.MESSAGE_BOX_INBOX;
- String senderId = null;
- if (!isOutgoing) {
- // We only need to find out sender phone number for received message
- senderId = getMmsSender(mms, cache);
- if (senderId == null) {
- LogUtil.w(TAG, "SyncMessagesAction: Could not find sender of incoming MMS "
- + "message " + mms.getUri() + "; using 'unknown sender' instead");
- senderId = ParticipantData.getUnknownSenderDestination();
- }
- }
- mms.setSender(senderId);
- }
- }
-
- /**
- * Find out the sender of an MMS message
- */
- private String getMmsSender(final MmsMessage mms, final ThreadInfoCache cache) {
- final List<String> recipients = cache.getThreadRecipients(mms.mThreadId);
- Assert.notNull(recipients);
- Assert.isTrue(recipients.size() > 0);
-
- if (recipients.size() == 1
- && recipients.get(0).equals(ParticipantData.getUnknownSenderDestination())) {
- LogUtil.w(TAG, "SyncMessagesAction: MMS message " + mms.mUri + " has unknown sender "
- + "(thread id = " + mms.mThreadId + ")");
- }
-
- return MmsUtils.getMmsSender(recipients, mms.mUri);
- }
-
- private SyncMessagesAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<SyncMessagesAction> CREATOR
- = new Parcelable.Creator<SyncMessagesAction>() {
- @Override
- public SyncMessagesAction createFromParcel(final Parcel in) {
- return new SyncMessagesAction(in);
- }
-
- @Override
- public SyncMessagesAction[] newArray(final int size) {
- return new SyncMessagesAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java b/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java
deleted file mode 100644
index 066ad74..0000000
--- a/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.action;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.util.Assert;
-
-public class UpdateConversationArchiveStatusAction extends Action {
-
- public static void archiveConversation(final String conversationId) {
- final UpdateConversationArchiveStatusAction action =
- new UpdateConversationArchiveStatusAction(conversationId, true /* isArchive */);
- action.start();
- }
-
- public static void unarchiveConversation(final String conversationId) {
- final UpdateConversationArchiveStatusAction action =
- new UpdateConversationArchiveStatusAction(conversationId, false /* isArchive */);
- action.start();
- }
-
- private static final String KEY_CONVERSATION_ID = "conversation_id";
- private static final String KEY_IS_ARCHIVE = "is_archive";
-
- protected UpdateConversationArchiveStatusAction(
- final String conversationId, final boolean isArchive) {
- Assert.isTrue(!TextUtils.isEmpty(conversationId));
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- actionParameters.putBoolean(KEY_IS_ARCHIVE, isArchive);
- }
-
- @Override
- protected Object executeAction() {
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- final boolean isArchived = actionParameters.getBoolean(KEY_IS_ARCHIVE);
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- try {
- BugleDatabaseOperations.updateConversationArchiveStatusInTransaction(
- db, conversationId, isArchived);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- MessagingContentProvider.notifyConversationListChanged();
- MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
- return null;
- }
-
- protected UpdateConversationArchiveStatusAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<UpdateConversationArchiveStatusAction> CREATOR
- = new Parcelable.Creator<UpdateConversationArchiveStatusAction>() {
- @Override
- public UpdateConversationArchiveStatusAction createFromParcel(final Parcel in) {
- return new UpdateConversationArchiveStatusAction(in);
- }
-
- @Override
- public UpdateConversationArchiveStatusAction[] newArray(final int size) {
- return new UpdateConversationArchiveStatusAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java b/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java
deleted file mode 100644
index 6c9e739..0000000
--- a/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.util.Assert;
-
-/**
- * Action used to update conversation options such as notification settings.
- */
-public class UpdateConversationOptionsAction extends Action
- implements Parcelable {
- /**
- * Enable/disable conversation notifications.
- */
- public static void enableConversationNotifications(final String conversationId,
- final boolean enableNotification) {
- Assert.notNull(conversationId);
-
- final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction(
- conversationId, enableNotification, null, null);
- action.start();
- }
-
- /**
- * Sets conversation notification sound.
- */
- public static void setConversationNotificationSound(final String conversationId,
- final String ringtoneUri) {
- Assert.notNull(conversationId);
-
- final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction(
- conversationId, null, ringtoneUri, null);
- action.start();
- }
-
- /**
- * Enable/disable vibrations for conversation notification.
- */
- public static void enableVibrationForConversationNotification(final String conversationId,
- final boolean enableVibration) {
- Assert.notNull(conversationId);
-
- final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction(
- conversationId, null, null, enableVibration);
- action.start();
- }
-
- private static final String KEY_CONVERSATION_ID = "conversation_id";
-
- // Keys for all settable settings.
- private static final String KEY_ENABLE_NOTIFICATION = "enable_notification";
- private static final String KEY_RINGTONE_URI = "ringtone_uri";
- private static final String KEY_ENABLE_VIBRATION = "enable_vibration";
-
- protected UpdateConversationOptionsAction(final String conversationId,
- final Boolean enableNotification, final String ringtoneUri,
- final Boolean enableVibration) {
- Assert.notNull(conversationId);
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- if (enableNotification != null) {
- actionParameters.putBoolean(KEY_ENABLE_NOTIFICATION, enableNotification);
- }
-
- if (ringtoneUri != null) {
- actionParameters.putString(KEY_RINGTONE_URI, ringtoneUri);
- }
-
- if (enableVibration != null) {
- actionParameters.putBoolean(KEY_ENABLE_VIBRATION, enableVibration);
- }
- }
-
- protected void putOptionValuesInTransaction(final ContentValues values,
- final DatabaseWrapper dbWrapper) {
- Assert.isTrue(dbWrapper.getDatabase().inTransaction());
- if (actionParameters.containsKey(KEY_ENABLE_NOTIFICATION)) {
- values.put(ConversationColumns.NOTIFICATION_ENABLED,
- actionParameters.getBoolean(KEY_ENABLE_NOTIFICATION));
- }
-
- if (actionParameters.containsKey(KEY_RINGTONE_URI)) {
- values.put(ConversationColumns.NOTIFICATION_SOUND_URI,
- actionParameters.getString(KEY_RINGTONE_URI));
- }
-
- if (actionParameters.containsKey(KEY_ENABLE_VIBRATION)) {
- values.put(ConversationColumns.NOTIFICATION_VIBRATION,
- actionParameters.getBoolean(KEY_ENABLE_VIBRATION));
- }
- }
-
- @Override
- protected Object executeAction() {
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- try {
- final ContentValues values = new ContentValues();
- putOptionValuesInTransaction(values, db);
-
- BugleDatabaseOperations.updateConversationRowIfExists(db, conversationId, values);
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
- return null;
- }
-
- protected UpdateConversationOptionsAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<UpdateConversationOptionsAction> CREATOR
- = new Parcelable.Creator<UpdateConversationOptionsAction>() {
- @Override
- public UpdateConversationOptionsAction createFromParcel(final Parcel in) {
- return new UpdateConversationOptionsAction(in);
- }
-
- @Override
- public UpdateConversationOptionsAction[] newArray(final int size) {
- return new UpdateConversationOptionsAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java b/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java
deleted file mode 100644
index c74096d..0000000
--- a/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.action;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.util.Assert;
-
-public class UpdateDestinationBlockedAction extends Action {
- public interface UpdateDestinationBlockedActionListener {
- @Assert.RunsOnMainThread
- abstract void onUpdateDestinationBlockedAction(final UpdateDestinationBlockedAction action,
- final boolean success,
- final boolean block,
- final String destination);
- }
-
- public static class UpdateDestinationBlockedActionMonitor extends ActionMonitor
- implements ActionMonitor.ActionCompletedListener {
- private final UpdateDestinationBlockedActionListener mListener;
-
- public UpdateDestinationBlockedActionMonitor(
- Object data, UpdateDestinationBlockedActionListener mListener) {
- super(STATE_CREATED, generateUniqueActionKey("UpdateDestinationBlockedAction"), data);
- setCompletedListener(this);
- this.mListener = mListener;
- }
-
- private void onActionDone(final boolean succeeded,
- final ActionMonitor monitor,
- final Action action,
- final Object data,
- final Object result) {
- mListener.onUpdateDestinationBlockedAction(
- (UpdateDestinationBlockedAction) action,
- succeeded,
- action.actionParameters.getBoolean(KEY_BLOCKED),
- action.actionParameters.getString(KEY_DESTINATION));
- }
-
- @Override
- public void onActionSucceeded(final ActionMonitor monitor,
- final Action action,
- final Object data,
- final Object result) {
- onActionDone(true, monitor, action, data, result);
- }
-
- @Override
- public void onActionFailed(final ActionMonitor monitor,
- final Action action,
- final Object data,
- final Object result) {
- onActionDone(false, monitor, action, data, result);
- }
- }
-
-
- public static UpdateDestinationBlockedActionMonitor updateDestinationBlocked(
- final String destination, final boolean blocked, final String conversationId,
- final UpdateDestinationBlockedActionListener listener) {
- Assert.notNull(listener);
- final UpdateDestinationBlockedActionMonitor monitor =
- new UpdateDestinationBlockedActionMonitor(null, listener);
- final UpdateDestinationBlockedAction action =
- new UpdateDestinationBlockedAction(destination, blocked, conversationId,
- monitor.getActionKey());
- action.start(monitor);
- return monitor;
- }
-
- private static final String KEY_CONVERSATION_ID = "conversation_id";
- private static final String KEY_DESTINATION = "destination";
- private static final String KEY_BLOCKED = "blocked";
-
- protected UpdateDestinationBlockedAction(
- final String destination, final boolean blocked, final String conversationId,
- final String actionKey) {
- super(actionKey);
- Assert.isTrue(!TextUtils.isEmpty(destination));
- actionParameters.putString(KEY_DESTINATION, destination);
- actionParameters.putBoolean(KEY_BLOCKED, blocked);
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- }
-
- @Override
- protected Object executeAction() {
- final String destination = actionParameters.getString(KEY_DESTINATION);
- final boolean isBlocked = actionParameters.getBoolean(KEY_BLOCKED);
- String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- final DatabaseWrapper db = DataModel.get().getDatabase();
- BugleDatabaseOperations.updateDestination(db, destination, isBlocked);
- if (conversationId == null) {
- conversationId = BugleDatabaseOperations
- .getConversationFromOtherParticipantDestination(db, destination);
- }
- if (conversationId != null) {
- if (isBlocked) {
- UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
- } else {
- UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
- }
- MessagingContentProvider.notifyParticipantsChanged(conversationId);
- }
- return null;
- }
-
- protected UpdateDestinationBlockedAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<UpdateDestinationBlockedAction> CREATOR
- = new Parcelable.Creator<UpdateDestinationBlockedAction>() {
- @Override
- public UpdateDestinationBlockedAction createFromParcel(final Parcel in) {
- return new UpdateDestinationBlockedAction(in);
- }
-
- @Override
- public UpdateDestinationBlockedAction[] newArray(final int size) {
- return new UpdateDestinationBlockedAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java b/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java
deleted file mode 100644
index 94e6f3b..0000000
--- a/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.action;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.BugleNotifications;
-
-/**
- * Updates the message notification (generally, to include voice replies we've
- * made since the notification was first posted).
- */
-public class UpdateMessageNotificationAction extends Action {
-
- public static void updateMessageNotification() {
- new UpdateMessageNotificationAction().start();
- }
-
- private UpdateMessageNotificationAction() {
- }
-
- @Override
- protected Object executeAction() {
- BugleNotifications.update(true /* silent */, BugleNotifications.UPDATE_MESSAGES);
- return null;
- }
-
- private UpdateMessageNotificationAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<UpdateMessageNotificationAction> CREATOR
- = new Parcelable.Creator<UpdateMessageNotificationAction>() {
- @Override
- public UpdateMessageNotificationAction createFromParcel(final Parcel in) {
- return new UpdateMessageNotificationAction(in);
- }
-
- @Override
- public UpdateMessageNotificationAction[] newArray(final int size) {
- return new UpdateMessageNotificationAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java b/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java
deleted file mode 100644
index 273dce9..0000000
--- a/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
-import com.android.messaging.util.Assert;
-
-/**
- * Action used to update size fields of a single part
- */
-public class UpdateMessagePartSizeAction extends Action implements Parcelable {
- /**
- * Update size of part
- */
- public static void updateSize(final String partId, final int width, final int height) {
- Assert.notNull(partId);
- Assert.inRange(width, 0, Integer.MAX_VALUE);
- Assert.inRange(height, 0, Integer.MAX_VALUE);
-
- final UpdateMessagePartSizeAction action = new UpdateMessagePartSizeAction(
- partId, width, height);
- action.start();
- }
-
- private static final String KEY_PART_ID = "part_id";
- private static final String KEY_WIDTH = "width";
- private static final String KEY_HEIGHT = "height";
-
- private UpdateMessagePartSizeAction(final String partId, final int width, final int height) {
- actionParameters.putString(KEY_PART_ID, partId);
- actionParameters.putInt(KEY_WIDTH, width);
- actionParameters.putInt(KEY_HEIGHT, height);
- }
-
- @Override
- protected Object executeAction() {
- final String partId = actionParameters.getString(KEY_PART_ID);
- final int width = actionParameters.getInt(KEY_WIDTH);
- final int height = actionParameters.getInt(KEY_HEIGHT);
-
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- try {
- final ContentValues values = new ContentValues();
-
- values.put(PartColumns.WIDTH, width);
- values.put(PartColumns.HEIGHT, height);
-
- // Part may have been deleted so allow update to fail without asserting
- BugleDatabaseOperations.updateRowIfExists(db, DatabaseHelper.PARTS_TABLE,
- PartColumns._ID, partId, values);
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- return null;
- }
-
- private UpdateMessagePartSizeAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<UpdateMessagePartSizeAction> CREATOR
- = new Parcelable.Creator<UpdateMessagePartSizeAction>() {
- @Override
- public UpdateMessagePartSizeAction createFromParcel(final Parcel in) {
- return new UpdateMessagePartSizeAction(in);
- }
-
- @Override
- public UpdateMessagePartSizeAction[] newArray(final int size) {
- return new UpdateMessagePartSizeAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java b/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java
deleted file mode 100644
index c1f39e1..0000000
--- a/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.action;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.LogUtil;
-
-public class WriteDraftMessageAction extends Action implements Parcelable {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
-
- /**
- * Set draft message (no listener)
- */
- public static void writeDraftMessage(final String conversationId, final MessageData message) {
- final WriteDraftMessageAction action = new WriteDraftMessageAction(conversationId, message);
- action.start();
- }
-
- private static final String KEY_CONVERSATION_ID = "conversationId";
- private static final String KEY_MESSAGE = "message";
-
- private WriteDraftMessageAction(final String conversationId, final MessageData message) {
- actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
- actionParameters.putParcelable(KEY_MESSAGE, message);
- }
-
- @Override
- protected Object executeAction() {
- final DatabaseWrapper db = DataModel.get().getDatabase();
- final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
- final MessageData message = actionParameters.getParcelable(KEY_MESSAGE);
- if (message.getSelfId() == null || message.getParticipantId() == null) {
- // This could happen when this occurs before the draft message is loaded
- // In this case, we just use the conversation's current self id as draft's
- // self id and/or participant id
- final ConversationListItemData conversation =
- ConversationListItemData.getExistingConversation(db, conversationId);
- if (conversation != null) {
- final String senderAndSelf = conversation.getSelfId();
- if (message.getSelfId() == null) {
- message.bindSelfId(senderAndSelf);
- }
- if (message.getParticipantId() == null) {
- message.bindParticipantId(senderAndSelf);
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_DATAMODEL_TAG, "Conversation " + conversationId +
- "already deleted before saving draft message " +
- message.getMessageId() + ". Aborting WriteDraftMessageAction.");
- return null;
- }
- }
- // Drafts are only kept in the local DB...
- final String messageId = BugleDatabaseOperations.updateDraftMessageData(
- db, conversationId, message, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
- MessagingContentProvider.notifyConversationListChanged();
- MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
- return messageId;
- }
-
- private WriteDraftMessageAction(final Parcel in) {
- super(in);
- }
-
- public static final Parcelable.Creator<WriteDraftMessageAction> CREATOR
- = new Parcelable.Creator<WriteDraftMessageAction>() {
- @Override
- public WriteDraftMessageAction createFromParcel(final Parcel in) {
- return new WriteDraftMessageAction(in);
- }
-
- @Override
- public WriteDraftMessageAction[] newArray(final int size) {
- return new WriteDraftMessageAction[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel parcel, final int flags) {
- writeActionToParcel(parcel, flags);
- }
-}
diff --git a/src/com/android/messaging/datamodel/binding/BindableData.java b/src/com/android/messaging/datamodel/binding/BindableData.java
deleted file mode 100644
index 5446098..0000000
--- a/src/com/android/messaging/datamodel/binding/BindableData.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.binding;
-
-/**
- * Base class for data objects that will be bound to a piece of the UI
- */
-public abstract class BindableData {
- /**
- * Called by Binding during unbind to allow data to proactively unregister callbacks
- * Data instance should release all listeners that may call back to the host UI
- */
- protected abstract void unregisterListeners();
-
- /**
- * Key used to identify the piece of UI that the data is currently bound to
- */
- private String mBindingId;
-
- /**
- * Bind this data to the ui host - checks data is currently unbound
- */
- public void bind(final String bindingId) {
- if (isBound() || bindingId == null) {
- throw new IllegalStateException();
- }
- mBindingId = bindingId;
- }
-
- /**
- * Unbind this data from the ui host - checks that the data is currently bound to specified id
- */
- public void unbind(final String bindingId) {
- if (!isBound(bindingId)) {
- throw new IllegalStateException();
- }
- unregisterListeners();
- mBindingId = null;
- }
-
- /**
- * Check to see if the data is bound to anything
- *
- * TODO: This should be package private because it's supposed to only be used by Binding,
- * however, several classes call this directly. We want the classes to track what they are
- * bound to.
- */
- protected boolean isBound() {
- return (mBindingId != null);
- }
-
- /**
- * Check to see if data is still bound with specified bindingId before calling over to ui
- */
- public boolean isBound(final String bindingId) {
- return (bindingId.equals(mBindingId));
- }
-}
diff --git a/src/com/android/messaging/datamodel/binding/BindableOnceData.java b/src/com/android/messaging/datamodel/binding/BindableOnceData.java
deleted file mode 100644
index 08e11da..0000000
--- a/src/com/android/messaging/datamodel/binding/BindableOnceData.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.binding;
-
-/**
- * A BindableData that's only used to be bound once. If the client needs to rebind, it needs
- * to create a new instance of the BindableOnceData.
- */
-public abstract class BindableOnceData extends BindableData {
- private boolean boundOnce = false;
-
- @Override
- public void bind(final String bindingId) {
- // Ensures that we can't re-bind again after the first binding.
- if (boundOnce) {
- throw new IllegalStateException();
- }
- super.bind(bindingId);
- boundOnce = true;
- }
-
- /**
- * Checks if the instance is bound to anything.
- */
- @Override
- public boolean isBound() {
- return super.isBound();
- }
-}
diff --git a/src/com/android/messaging/datamodel/binding/Binding.java b/src/com/android/messaging/datamodel/binding/Binding.java
deleted file mode 100644
index 3ec01dd..0000000
--- a/src/com/android/messaging/datamodel/binding/Binding.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.binding;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-public class Binding<T extends BindableData> extends BindingBase<T> {
- private static AtomicLong sBindingIdx = new AtomicLong(System.currentTimeMillis() * 1000);
-
- private String mBindingId;
- private T mData;
- private final Object mOwner;
- private boolean mWasBound;
-
- /**
- * Initialize a binding instance - the owner is typically the containing class
- */
- Binding(final Object owner) {
- mOwner = owner;
- }
-
- @Override
- public T getData() {
- ensureBound();
- return mData;
- }
-
- @Override
- public boolean isBound() {
- return (mData != null && mData.isBound(mBindingId));
- }
-
- @Override
- public boolean isBound(final T data) {
- return (isBound() && data == mData);
- }
-
- @Override
- public void ensureBound() {
- if (!isBound()) {
- throw new IllegalStateException("not bound; wasBound = " + mWasBound);
- }
- }
-
- @Override
- public void ensureBound(final T data) {
- if (!isBound()) {
- throw new IllegalStateException("not bound; wasBound = " + mWasBound);
- } else if (data != mData) {
- throw new IllegalStateException("not bound to correct data " + data + " vs " + mData);
- }
- }
-
- @Override
- public String getBindingId() {
- return mBindingId;
- }
-
- public void bind(final T data) {
- // Check both this binding and the data not already bound
- if (mData != null || data.isBound()) {
- throw new IllegalStateException("already bound when binding to " + data);
- }
- // Generate a unique identifier for this bind call
- mBindingId = Long.toHexString(sBindingIdx.getAndIncrement());
- data.bind(mBindingId);
- mData = data;
- mWasBound = true;
- }
-
- public void unbind() {
- // Check this binding is bound and that data is bound to this binding
- if (mData == null || !mData.isBound(mBindingId)) {
- throw new IllegalStateException("not bound when unbind");
- }
- mData.unbind(mBindingId);
- mData = null;
- mBindingId = null;
- }
-}
diff --git a/src/com/android/messaging/datamodel/binding/BindingBase.java b/src/com/android/messaging/datamodel/binding/BindingBase.java
deleted file mode 100644
index 3d6da9b..0000000
--- a/src/com/android/messaging/datamodel/binding/BindingBase.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.binding;
-
-/**
- * The binding class keeps track of a binding between a ui component and an item of BindableData
- * It allows each side to ensure that when it communicates with the other they are still bound
- * together.
- * NOTE: Ensure that the UI component uses the same binding instance for it's whole lifetime
- * (DO NOT CREATE A NEW BINDING EACH TIME A NEW PIECE OF DATA IS BOUND)...
- *
- * The ui component owns the binding instance.
- * It can use it [isBound(data)] to see if the binding still binds to the right piece of data
- *
- * Upon binding the data is informed of a unique binding key generated in this class and can use
- * that to ensure that it is still issuing callbacks to the right piece of ui.
- */
-public abstract class BindingBase<T extends BindableData> {
- /**
- * Creates a new exclusively owned binding for the owner object.
- */
- public static <T extends BindableData> Binding<T> createBinding(final Object owner) {
- return new Binding<T>(owner);
- }
-
- /**
- * Creates a new read-only binding referencing the source binding object.
- * TODO: We may want to refcount the Binding references, so that when the binding owner
- * calls unbind() when there's still outstanding references we can catch it.
- */
- public static <T extends BindableData> ImmutableBindingRef<T> createBindingReference(
- final BindingBase<T> srcBinding) {
- return new ImmutableBindingRef<T>(srcBinding);
- }
-
- /**
- * Creates a detachable binding for the owner object. Use this if your owner object is a UI
- * component that may undergo a "detached from window" -> "re-attached to window" transition.
- */
- public static <T extends BindableData> DetachableBinding<T> createDetachableBinding(
- final Object owner) {
- return new DetachableBinding<T>(owner);
- }
-
- public abstract T getData();
-
- /**
- * Check if binding connects to the specified data instance
- */
- public abstract boolean isBound();
-
- /**
- * Check if binding connects to the specified data instance
- */
- public abstract boolean isBound(final T data);
-
- /**
- * Throw if binding connects to the specified data instance
- */
- public abstract void ensureBound();
-
- /**
- * Throw if binding connects to the specified data instance
- */
- public abstract void ensureBound(final T data);
-
- /**
- * Return the binding id for this binding (will be null if not bound)
- */
- public abstract String getBindingId();
-}
diff --git a/src/com/android/messaging/datamodel/binding/DetachableBinding.java b/src/com/android/messaging/datamodel/binding/DetachableBinding.java
deleted file mode 100644
index a414c3b..0000000
--- a/src/com/android/messaging/datamodel/binding/DetachableBinding.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.binding;
-
-import com.android.messaging.util.Assert;
-
-/**
- * An extension on {@link Binding} that allows for temporary data detachment from the UI component.
- * This is used when, instead of destruction or data rebinding, the owning UI undergoes a
- * "detached from window" -> "re-attached to window" transition, in which case we want to
- * temporarily unbind the data and remember it so that it can be rebound when the UI is re-attached
- * to window later.
- */
-public class DetachableBinding<T extends BindableData> extends Binding<T> {
- private T mDetachedData;
-
- DetachableBinding(Object owner) {
- super(owner);
- }
-
- @Override
- public void bind(T data) {
- super.bind(data);
- // Rebinding before re-attaching. Pre-emptively throw away the detached data because
- // it's now stale.
- mDetachedData = null;
- }
-
- public void detach() {
- Assert.isNull(mDetachedData);
- Assert.isTrue(isBound());
- mDetachedData = getData();
- unbind();
- }
-
- public void reAttachIfPossible() {
- if (mDetachedData != null) {
- Assert.isFalse(isBound());
- bind(mDetachedData);
- mDetachedData = null;
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java b/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java
deleted file mode 100644
index 9a0a3d6..0000000
--- a/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.binding;
-
-import com.android.messaging.util.Assert;
-
-/**
- * A immutable wrapper around a Binding object. Callers can only access readonly methods like
- * getData(), isBound() and ensureBound() but not bind() and unbind(). This is used for MVC pattern
- * where both the View and the Controller needs access to a centrally bound Model object. The View
- * is the one that owns the bind/unbind logic of the Binding, whereas controller only serves as a
- * consumer.
- */
-public class ImmutableBindingRef<T extends BindableData> extends BindingBase<T> {
- /**
- * The referenced, read-only binding object.
- */
- private final BindingBase<T> mBinding;
-
- /**
- * Hidden ctor.
- */
- ImmutableBindingRef(final BindingBase<T> binding) {
- mBinding = resolveBinding(binding);
- }
-
- @Override
- public T getData() {
- return mBinding.getData();
- }
-
- @Override
- public boolean isBound() {
- return mBinding.isBound();
- }
-
- @Override
- public boolean isBound(final T data) {
- return mBinding.isBound(data);
- }
-
- @Override
- public void ensureBound() {
- mBinding.ensureBound();
- }
-
- @Override
- public void ensureBound(final T data) {
- mBinding.ensureBound(data);
- }
-
- @Override
- public String getBindingId() {
- return mBinding.getBindingId();
- }
-
- /**
- * Resolve the source binding to the real BindingImpl it's referencing. This avoids the
- * redundancy of multiple wrapper calls when creating a binding reference from an existing
- * binding reference.
- */
- private BindingBase<T> resolveBinding(final BindingBase<T> binding) {
- BindingBase<T> resolvedBinding = binding;
- while (resolvedBinding instanceof ImmutableBindingRef<?>) {
- resolvedBinding = ((ImmutableBindingRef<T>) resolvedBinding).mBinding;
- }
- Assert.isTrue(resolvedBinding instanceof Binding<?>);
- return resolvedBinding;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java b/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java
deleted file mode 100644
index 4e94ee1..0000000
--- a/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.app.LoaderManager;
-import android.content.Context;
-import android.content.Loader;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-
-import com.android.messaging.datamodel.BoundCursorLoader;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.util.Assert;
-
-/**
- * Services data needs for BlockedParticipantsFragment
- */
-public class BlockedParticipantsData extends BindableData implements
- LoaderManager.LoaderCallbacks<Cursor> {
- public interface BlockedParticipantsDataListener {
- public void onBlockedParticipantsCursorUpdated(final Cursor cursor);
- }
- private static final String BINDING_ID = "bindingId";
- private static final int BLOCKED_PARTICIPANTS_LOADER = 1;
- private final Context mContext;
- private LoaderManager mLoaderManager;
- private BlockedParticipantsDataListener mListener;
-
- public BlockedParticipantsData(final Context context,
- final BlockedParticipantsDataListener listener) {
- mContext = context;
- mListener = listener;
- }
-
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- Assert.isTrue(id == BLOCKED_PARTICIPANTS_LOADER);
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- final Uri uri = MessagingContentProvider.PARTICIPANTS_URI;
- return new BoundCursorLoader(bindingId, mContext, uri,
- ParticipantData.ParticipantsQuery.PROJECTION,
- ParticipantColumns.BLOCKED + "=1", null, null);
- }
- return null;
- }
-
- @Override
- public void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor) {
- Assert.isTrue(loader.getId() == BLOCKED_PARTICIPANTS_LOADER);
- final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
- Assert.isTrue(isBound(cursorLoader.getBindingId()));
- mListener.onBlockedParticipantsCursorUpdated(cursor);
- }
-
- @Override
- public void onLoaderReset(final Loader<Cursor> loader) {
- Assert.isTrue(loader.getId() == BLOCKED_PARTICIPANTS_LOADER);
- final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
- Assert.isTrue(isBound(cursorLoader.getBindingId()));
- mListener.onBlockedParticipantsCursorUpdated(null);
- }
-
- public void init(final LoaderManager loaderManager,
- final BindingBase<BlockedParticipantsData> binding) {
- final Bundle args = new Bundle();
- args.putString(BINDING_ID, binding.getBindingId());
- mLoaderManager = loaderManager;
- mLoaderManager.initLoader(BLOCKED_PARTICIPANTS_LOADER, args, this);
- }
-
- @Override
- protected void unregisterListeners() {
- mListener = null;
- if (mLoaderManager != null) {
- mLoaderManager.destroyLoader(BLOCKED_PARTICIPANTS_LOADER);
- mLoaderManager = null;
- }
- }
-
- public ParticipantListItemData createParticipantListItemData(Cursor cursor) {
- return new ParticipantListItemData(ParticipantData.getFromCursor(cursor));
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/ContactListItemData.java b/src/com/android/messaging/datamodel/data/ContactListItemData.java
deleted file mode 100644
index dcc7e20..0000000
--- a/src/com/android/messaging/datamodel/data/ContactListItemData.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.DisplayNameSources;
-
-import com.android.ex.chips.RecipientEntry;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContactRecipientEntryUtils;
-import com.android.messaging.util.ContactUtil;
-
-/**
- * Data model object used to power ContactListItemViews, which may be displayed either in
- * our contact list, or in the chips UI search drop down presented by ContactDropdownLayouter.
- */
-public class ContactListItemData {
- // Keeps the contact data in the form of RecipientEntry that RecipientEditTextView can
- // directly use.
- private RecipientEntry mRecipientEntry;
-
- private CharSequence mStyledName;
- private CharSequence mStyledDestination;
-
- // If this contact is the first in the list for its first letter, then this will be the
- // first letter, otherwise this is null.
- private String mAlphabetHeader;
-
- // Is the contact the only item in the list (happens when the user clicks on an
- // existing chip for which we show full contact detail for the selected contact).
- private boolean mSingleRecipient;
-
- /**
- * Bind to a contact cursor in the contact list.
- */
- public void bind(final Cursor cursor, final String alphabetHeader) {
- final long dataId = cursor.getLong(ContactUtil.INDEX_DATA_ID);
- final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- final String lookupKey = cursor.getString(ContactUtil.INDEX_LOOKUP_KEY);
- final String displayName = cursor.getString(ContactUtil.INDEX_DISPLAY_NAME);
- final String photoThumbnailUri = cursor.getString(ContactUtil.INDEX_PHOTO_URI);
- final String destination = cursor.getString(ContactUtil.INDEX_PHONE_EMAIL);
- final int destinationType = cursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
- final String destinationLabel = cursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL);
- mStyledName = null;
- mStyledDestination = null;
- mAlphabetHeader = alphabetHeader;
- mSingleRecipient = false;
-
- // Check whether this contact is first level (i.e. whether it's the first entry of this
- // contact in the contact list).
- boolean isFirstLevel = true;
- if (!cursor.isFirst() && cursor.moveToPrevious()) {
- final long contactIdPrevious = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- if (contactId == contactIdPrevious) {
- isFirstLevel = false;
- }
- cursor.moveToNext();
- }
-
- mRecipientEntry = ContactUtil.createRecipientEntry(displayName,
- DisplayNameSources.STRUCTURED_NAME, destination, destinationType, destinationLabel,
- contactId, lookupKey, dataId, photoThumbnailUri, isFirstLevel);
- }
-
- /**
- * Bind to a RecipientEntry produced by the chips text view in the search drop down, plus
- * optional styled name & destination for showing bold search match.
- */
- public void bind(final RecipientEntry entry, final CharSequence styledName,
- final CharSequence styledDestination, final boolean singleRecipient) {
- Assert.isTrue(entry.isValid());
- mRecipientEntry = entry;
- mStyledName = styledName;
- mStyledDestination = styledDestination;
- mAlphabetHeader = null;
- mSingleRecipient = singleRecipient;
- }
-
- public CharSequence getDisplayName() {
- final CharSequence displayName = mStyledName != null ? mStyledName :
- ContactRecipientEntryUtils.getDisplayNameForContactList(mRecipientEntry);
- return displayName == null ? "" : displayName;
- }
-
- public Uri getPhotoThumbnailUri() {
- return mRecipientEntry.getPhotoThumbnailUri() == null ? null :
- mRecipientEntry.getPhotoThumbnailUri();
- }
-
- public CharSequence getDestination() {
- final CharSequence destination = mStyledDestination != null ?
- mStyledDestination : ContactRecipientEntryUtils.formatDestination(mRecipientEntry);
- return destination == null ? "" : destination;
- }
-
- public int getDestinationType() {
- return mRecipientEntry.getDestinationType();
- }
-
- public String getDestinationLabel() {
- return mRecipientEntry.getDestinationLabel();
- }
-
- public long getContactId() {
- return mRecipientEntry.getContactId();
- }
-
- public String getLookupKey() {
- return mRecipientEntry.getLookupKey();
- }
-
- /**
- * Returns if this item is "first-level," i.e. whether it's the first entry of the contact
- * that it represents in the list. For example, if John Smith has 3 different phone numbers,
- * then the first number is considered first-level, while the other two are considered
- * second-level.
- */
- public boolean getIsFirstLevel() {
- // Treat the item as first level if it's a top-level recipient entry, or if it's the only
- // item in the list.
- return mRecipientEntry.isFirstLevel() || mSingleRecipient;
- }
-
- /**
- * Returns if this item is simple, i.e. it has only avatar and a display name with phone number
- * embedded so we can hide everything else.
- */
- public boolean getIsSimpleContactItem() {
- return ContactRecipientEntryUtils.isAvatarAndNumberOnlyContact(mRecipientEntry) ||
- ContactRecipientEntryUtils.isSendToDestinationContact(mRecipientEntry);
- }
-
- public String getAlphabetHeader() {
- return mAlphabetHeader;
- }
-
- /**
- * Returns a RecipientEntry instance readily usable by the RecipientEditTextView.
- */
- public RecipientEntry getRecipientEntry() {
- return mRecipientEntry;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/ContactPickerData.java b/src/com/android/messaging/datamodel/data/ContactPickerData.java
deleted file mode 100644
index fd6fca0..0000000
--- a/src/com/android/messaging/datamodel/data/ContactPickerData.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.app.LoaderManager;
-import android.content.Context;
-import android.content.Loader;
-import android.database.Cursor;
-import android.os.Bundle;
-
-import com.android.messaging.datamodel.BoundCursorLoader;
-import com.android.messaging.datamodel.FrequentContactsCursorBuilder;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Class to access phone contacts.
- * The caller is responsible for ensuring that the app has READ_CONTACTS permission (see
- * {@link ContactUtil#hasReadContactsPermission()}) before instantiating this class.
- */
-public class ContactPickerData extends BindableData implements
- LoaderManager.LoaderCallbacks<Cursor> {
- public interface ContactPickerDataListener {
- void onAllContactsCursorUpdated(Cursor data);
- void onFrequentContactsCursorUpdated(Cursor data);
- void onContactCustomColorLoaded(ContactPickerData data);
- }
-
- private static final String BINDING_ID = "bindingId";
- private final Context mContext;
- private LoaderManager mLoaderManager;
- private ContactPickerDataListener mListener;
- private final FrequentContactsCursorBuilder mFrequentContactsCursorBuilder;
-
- public ContactPickerData(final Context context, final ContactPickerDataListener listener) {
- mListener = listener;
- mContext = context;
- mFrequentContactsCursorBuilder = new FrequentContactsCursorBuilder();
- }
-
- private static final int ALL_CONTACTS_LOADER = 1;
- private static final int FREQUENT_CONTACTS_LOADER = 2;
- private static final int PARTICIPANT_LOADER = 3;
-
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- switch (id) {
- case ALL_CONTACTS_LOADER:
- return ContactUtil.getPhones(mContext)
- .createBoundCursorLoader(bindingId);
- case FREQUENT_CONTACTS_LOADER:
- return ContactUtil.getFrequentContacts(mContext)
- .createBoundCursorLoader(bindingId);
- case PARTICIPANT_LOADER:
- return new BoundCursorLoader(bindingId, mContext,
- MessagingContentProvider.PARTICIPANTS_URI,
- ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
- default:
- Assert.fail("Unknown loader id for contact picker!");
- break;
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the contacts list");
- }
- return null;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
- final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
- if (isBound(cursorLoader.getBindingId())) {
- switch (loader.getId()) {
- case ALL_CONTACTS_LOADER:
- mListener.onAllContactsCursorUpdated(data);
- mFrequentContactsCursorBuilder.setAllContacts(data);
- break;
- case FREQUENT_CONTACTS_LOADER:
- mFrequentContactsCursorBuilder.setFrequents(data);
- break;
- case PARTICIPANT_LOADER:
- mListener.onContactCustomColorLoaded(this);
- break;
- default:
- Assert.fail("Unknown loader id for contact picker!");
- break;
- }
-
- if (loader.getId() != PARTICIPANT_LOADER) {
- // The frequent contacts cursor to be used in the UI depends on results from both
- // all contacts and frequent contacts loader, and we don't know which will finish
- // first. Therefore, try to build the cursor and notify the listener if it's
- // successfully built.
- final Cursor frequentContactsCursor = mFrequentContactsCursorBuilder.build();
- if (frequentContactsCursor != null) {
- mListener.onFrequentContactsCursorUpdated(frequentContactsCursor);
- }
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the contacts list");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoaderReset(final Loader<Cursor> loader) {
- final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
- if (isBound(cursorLoader.getBindingId())) {
- switch (loader.getId()) {
- case ALL_CONTACTS_LOADER:
- mListener.onAllContactsCursorUpdated(null);
- mFrequentContactsCursorBuilder.setAllContacts(null);
- break;
- case FREQUENT_CONTACTS_LOADER:
- mListener.onFrequentContactsCursorUpdated(null);
- mFrequentContactsCursorBuilder.setFrequents(null);
- break;
- case PARTICIPANT_LOADER:
- mListener.onContactCustomColorLoaded(this);
- break;
- default:
- Assert.fail("Unknown loader id for contact picker!");
- break;
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the contacts list");
- }
- }
-
- public void init(final LoaderManager loaderManager,
- final BindingBase<ContactPickerData> binding) {
- final Bundle args = new Bundle();
- args.putString(BINDING_ID, binding.getBindingId());
- mLoaderManager = loaderManager;
- mLoaderManager.initLoader(ALL_CONTACTS_LOADER, args, this);
- mLoaderManager.initLoader(FREQUENT_CONTACTS_LOADER, args, this);
- mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this);
- }
-
- @Override
- protected void unregisterListeners() {
- mListener = null;
-
-
- // This could be null if we bind but the caller doesn't init the BindableData
- if (mLoaderManager != null) {
- mLoaderManager.destroyLoader(ALL_CONTACTS_LOADER);
- mLoaderManager.destroyLoader(FREQUENT_CONTACTS_LOADER);
- mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
- mLoaderManager = null;
- }
- mFrequentContactsCursorBuilder.resetBuilder();
- }
-
- public static boolean isTooManyParticipants(final int participantCount) {
- // When creating a conversation, the conversation will be created using the system's
- // default SIM, so use the default MmsConfig's recipient limit.
- return (participantCount > MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
- .getRecipientLimit());
- }
-
- public static boolean getCanAddMoreParticipants(final int participantCount) {
- // When creating a conversation, the conversation will be created using the system's
- // default SIM, so use the default MmsConfig's recipient limit.
- return (participantCount < MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
- .getRecipientLimit());
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/ConversationData.java b/src/com/android/messaging/datamodel/data/ConversationData.java
deleted file mode 100644
index d504928..0000000
--- a/src/com/android/messaging/datamodel/data/ConversationData.java
+++ /dev/null
@@ -1,849 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.app.LoaderManager;
-import android.content.Context;
-import android.content.Loader;
-import android.database.Cursor;
-import android.database.CursorWrapper;
-import android.database.sqlite.SQLiteFullException;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-
-import com.android.common.contacts.DataUsageStatUpdater;
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.BoundCursorLoader;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.action.DeleteConversationAction;
-import com.android.messaging.datamodel.action.DeleteMessageAction;
-import com.android.messaging.datamodel.action.InsertNewMessageAction;
-import com.android.messaging.datamodel.action.RedownloadMmsAction;
-import com.android.messaging.datamodel.action.ResendMessageAction;
-import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.widget.WidgetConversationProvider;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class ConversationData extends BindableData {
-
- private static final String TAG = "bugle_datamodel";
- private static final String BINDING_ID = "bindingId";
- private static final long LAST_MESSAGE_TIMESTAMP_NaN = -1;
- private static final int MESSAGE_COUNT_NaN = -1;
-
- /**
- * Takes a conversation id and a list of message ids and computes the positions
- * for each message.
- */
- public List<Integer> getPositions(final String conversationId, final List<Long> ids) {
- final ArrayList<Integer> result = new ArrayList<Integer>();
-
- if (ids.isEmpty()) {
- return result;
- }
-
- final Cursor c = new ConversationData.ReversedCursor(
- DataModel.get().getDatabase().rawQuery(
- ConversationMessageData.getConversationMessageIdsQuerySql(),
- new String [] { conversationId }));
- if (c != null) {
- try {
- final Set<Long> idsSet = new HashSet<Long>(ids);
- if (c.moveToLast()) {
- do {
- final long messageId = c.getLong(0);
- if (idsSet.contains(messageId)) {
- result.add(c.getPosition());
- }
- } while (c.moveToPrevious());
- }
- } finally {
- c.close();
- }
- }
- Collections.sort(result);
- return result;
- }
-
- public interface ConversationDataListener {
- public void onConversationMessagesCursorUpdated(ConversationData data, Cursor cursor,
- @Nullable ConversationMessageData newestMessage, boolean isSync);
- public void onConversationMetadataUpdated(ConversationData data);
- public void closeConversation(String conversationId);
- public void onConversationParticipantDataLoaded(ConversationData data);
- public void onSubscriptionListDataLoaded(ConversationData data);
- }
-
- private static class ReversedCursor extends CursorWrapper {
- final int mCount;
-
- public ReversedCursor(final Cursor cursor) {
- super(cursor);
- mCount = cursor.getCount();
- }
-
- @Override
- public boolean moveToPosition(final int position) {
- return super.moveToPosition(mCount - position - 1);
- }
-
- @Override
- public int getPosition() {
- return mCount - super.getPosition() - 1;
- }
-
- @Override
- public boolean isAfterLast() {
- return super.isBeforeFirst();
- }
-
- @Override
- public boolean isBeforeFirst() {
- return super.isAfterLast();
- }
-
- @Override
- public boolean isFirst() {
- return super.isLast();
- }
-
- @Override
- public boolean isLast() {
- return super.isFirst();
- }
-
- @Override
- public boolean move(final int offset) {
- return super.move(-offset);
- }
-
- @Override
- public boolean moveToFirst() {
- return super.moveToLast();
- }
-
- @Override
- public boolean moveToLast() {
- return super.moveToFirst();
- }
-
- @Override
- public boolean moveToNext() {
- return super.moveToPrevious();
- }
-
- @Override
- public boolean moveToPrevious() {
- return super.moveToNext();
- }
- }
-
- /**
- * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
- */
- private class MetadataLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- Assert.equals(CONVERSATION_META_DATA_LOADER, id);
- Loader<Cursor> loader = null;
-
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- final Uri uri =
- MessagingContentProvider.buildConversationMetadataUri(mConversationId);
- loader = new BoundCursorLoader(bindingId, mContext, uri,
- ConversationListItemData.PROJECTION, null, null, null);
- } else {
- LogUtil.w(TAG, "Creating messages loader after unbinding mConversationId = " +
- mConversationId);
- }
- return loader;
- }
-
- @Override
- public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- if (data.moveToNext()) {
- Assert.isTrue(data.getCount() == 1);
- mConversationMetadata.bind(data);
- mListeners.onConversationMetadataUpdated(ConversationData.this);
- } else {
- // Close the conversation, no meta data means conversation was deleted
- LogUtil.w(TAG, "Meta data loader returned nothing for mConversationId = " +
- mConversationId);
- mListeners.closeConversation(mConversationId);
- // Notify the widget the conversation is deleted so it can go into its
- // configure state.
- WidgetConversationProvider.notifyConversationDeleted(
- Factory.get().getApplicationContext(),
- mConversationId);
- }
- } else {
- LogUtil.w(TAG, "Meta data loader finished after unbinding mConversationId = " +
- mConversationId);
- }
- }
-
- @Override
- public void onLoaderReset(final Loader<Cursor> generic) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- // Clear the conversation meta data
- mConversationMetadata = new ConversationListItemData();
- mListeners.onConversationMetadataUpdated(ConversationData.this);
- } else {
- LogUtil.w(TAG, "Meta data loader reset after unbinding mConversationId = " +
- mConversationId);
- }
- }
- }
-
- /**
- * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
- */
- private class MessagesLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- Assert.equals(CONVERSATION_MESSAGES_LOADER, id);
- Loader<Cursor> loader = null;
-
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- final Uri uri =
- MessagingContentProvider.buildConversationMessagesUri(mConversationId);
- loader = new BoundCursorLoader(bindingId, mContext, uri,
- ConversationMessageData.getProjection(), null, null, null);
- mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
- mMessageCount = MESSAGE_COUNT_NaN;
- } else {
- LogUtil.w(TAG, "Creating messages loader after unbinding mConversationId = " +
- mConversationId);
- }
- return loader;
- }
-
- @Override
- public void onLoadFinished(final Loader<Cursor> generic, final Cursor rawData) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- // Check if we have a new message, or if we had a message sync.
- ConversationMessageData newMessage = null;
- boolean isSync = false;
- Cursor data = null;
- if (rawData != null) {
- // Note that the cursor is sorted DESC so here we reverse it.
- // This is a performance issue (improvement) for large cursors.
- data = new ReversedCursor(rawData);
-
- final int messageCountOld = mMessageCount;
- mMessageCount = data.getCount();
- final ConversationMessageData lastMessage = getLastMessage(data);
- if (lastMessage != null) {
- final long lastMessageTimestampOld = mLastMessageTimestamp;
- mLastMessageTimestamp = lastMessage.getReceivedTimeStamp();
- final String lastMessageIdOld = mLastMessageId;
- mLastMessageId = lastMessage.getMessageId();
- if (TextUtils.equals(lastMessageIdOld, mLastMessageId) &&
- messageCountOld < mMessageCount) {
- // Last message stays the same (no incoming message) but message
- // count increased, which means there has been a message sync.
- isSync = true;
- } else if (messageCountOld != MESSAGE_COUNT_NaN && // Ignore initial load
- mLastMessageTimestamp != LAST_MESSAGE_TIMESTAMP_NaN &&
- mLastMessageTimestamp > lastMessageTimestampOld) {
- newMessage = lastMessage;
- }
- } else {
- mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
- }
- } else {
- mMessageCount = MESSAGE_COUNT_NaN;
- }
-
- mListeners.onConversationMessagesCursorUpdated(ConversationData.this, data,
- newMessage, isSync);
- } else {
- LogUtil.w(TAG, "Messages loader finished after unbinding mConversationId = " +
- mConversationId);
- }
- }
-
- @Override
- public void onLoaderReset(final Loader<Cursor> generic) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- mListeners.onConversationMessagesCursorUpdated(ConversationData.this, null, null,
- false);
- mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
- mMessageCount = MESSAGE_COUNT_NaN;
- } else {
- LogUtil.w(TAG, "Messages loader reset after unbinding mConversationId = " +
- mConversationId);
- }
- }
-
- private ConversationMessageData getLastMessage(final Cursor cursor) {
- if (cursor != null && cursor.getCount() > 0) {
- final int position = cursor.getPosition();
- if (cursor.moveToLast()) {
- final ConversationMessageData messageData = new ConversationMessageData();
- messageData.bind(cursor);
- cursor.move(position);
- return messageData;
- }
- }
- return null;
- }
- }
-
- /**
- * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
- */
- private class ParticipantLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- Assert.equals(PARTICIPANT_LOADER, id);
- Loader<Cursor> loader = null;
-
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- final Uri uri =
- MessagingContentProvider.buildConversationParticipantsUri(mConversationId);
- loader = new BoundCursorLoader(bindingId, mContext, uri,
- ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
- } else {
- LogUtil.w(TAG, "Creating participant loader after unbinding mConversationId = " +
- mConversationId);
- }
- return loader;
- }
-
- @Override
- public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- mParticipantData.bind(data);
- mListeners.onConversationParticipantDataLoaded(ConversationData.this);
- } else {
- LogUtil.w(TAG, "Participant loader finished after unbinding mConversationId = " +
- mConversationId);
- }
- }
-
- @Override
- public void onLoaderReset(final Loader<Cursor> generic) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- mParticipantData.bind(null);
- } else {
- LogUtil.w(TAG, "Participant loader reset after unbinding mConversationId = " +
- mConversationId);
- }
- }
- }
-
- /**
- * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
- */
- private class SelfParticipantLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- Assert.equals(SELF_PARTICIPANT_LOADER, id);
- Loader<Cursor> loader = null;
-
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- loader = new BoundCursorLoader(bindingId, mContext,
- MessagingContentProvider.PARTICIPANTS_URI,
- ParticipantData.ParticipantsQuery.PROJECTION,
- ParticipantColumns.SUB_ID + " <> ?",
- new String[] { String.valueOf(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
- null);
- } else {
- LogUtil.w(TAG, "Creating self loader after unbinding mConversationId = " +
- mConversationId);
- }
- return loader;
- }
-
- @Override
- public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- mSelfParticipantsData.bind(data);
- mSubscriptionListData.bind(mSelfParticipantsData.getSelfParticipants(true));
- mListeners.onSubscriptionListDataLoaded(ConversationData.this);
- } else {
- LogUtil.w(TAG, "Self loader finished after unbinding mConversationId = " +
- mConversationId);
- }
- }
-
- @Override
- public void onLoaderReset(final Loader<Cursor> generic) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- mSelfParticipantsData.bind(null);
- } else {
- LogUtil.w(TAG, "Self loader reset after unbinding mConversationId = " +
- mConversationId);
- }
- }
- }
-
- private final ConversationDataEventDispatcher mListeners;
- private final MetadataLoaderCallbacks mMetadataLoaderCallbacks;
- private final MessagesLoaderCallbacks mMessagesLoaderCallbacks;
- private final ParticipantLoaderCallbacks mParticipantsLoaderCallbacks;
- private final SelfParticipantLoaderCallbacks mSelfParticipantLoaderCallbacks;
- private final Context mContext;
- private final String mConversationId;
- private final ConversationParticipantsData mParticipantData;
- private final SelfParticipantsData mSelfParticipantsData;
- private ConversationListItemData mConversationMetadata;
- private final SubscriptionListData mSubscriptionListData;
- private LoaderManager mLoaderManager;
- private long mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
- private int mMessageCount = MESSAGE_COUNT_NaN;
- private String mLastMessageId;
-
- public ConversationData(final Context context, final ConversationDataListener listener,
- final String conversationId) {
- Assert.isTrue(conversationId != null);
- mContext = context;
- mConversationId = conversationId;
- mMetadataLoaderCallbacks = new MetadataLoaderCallbacks();
- mMessagesLoaderCallbacks = new MessagesLoaderCallbacks();
- mParticipantsLoaderCallbacks = new ParticipantLoaderCallbacks();
- mSelfParticipantLoaderCallbacks = new SelfParticipantLoaderCallbacks();
- mParticipantData = new ConversationParticipantsData();
- mConversationMetadata = new ConversationListItemData();
- mSelfParticipantsData = new SelfParticipantsData();
- mSubscriptionListData = new SubscriptionListData(context);
-
- mListeners = new ConversationDataEventDispatcher();
- mListeners.add(listener);
- }
-
- @RunsOnMainThread
- public void addConversationDataListener(final ConversationDataListener listener) {
- Assert.isMainThread();
- mListeners.add(listener);
- }
-
- public String getConversationName() {
- return mConversationMetadata.getName();
- }
-
- public boolean getIsArchived() {
- return mConversationMetadata.getIsArchived();
- }
-
- public String getIcon() {
- return mConversationMetadata.getIcon();
- }
-
- public String getConversationId() {
- return mConversationId;
- }
-
- public void setFocus() {
- DataModel.get().setFocusedConversation(mConversationId);
- // As we are loading the conversation assume the user has read the messages...
- // Do this late though so that it doesn't get in the way of other actions
- BugleNotifications.markMessagesAsRead(mConversationId);
- }
-
- public void unsetFocus() {
- DataModel.get().setFocusedConversation(null);
- }
-
- public boolean isFocused() {
- return isBound() && DataModel.get().isFocusedConversation(mConversationId);
- }
-
- private static final int CONVERSATION_META_DATA_LOADER = 1;
- private static final int CONVERSATION_MESSAGES_LOADER = 2;
- private static final int PARTICIPANT_LOADER = 3;
- private static final int SELF_PARTICIPANT_LOADER = 4;
-
- public void init(final LoaderManager loaderManager,
- final BindingBase<ConversationData> binding) {
- // Remember the binding id so that loader callbacks can check if data is still bound
- // to same ui component
- final Bundle args = new Bundle();
- args.putString(BINDING_ID, binding.getBindingId());
- mLoaderManager = loaderManager;
- mLoaderManager.initLoader(CONVERSATION_META_DATA_LOADER, args, mMetadataLoaderCallbacks);
- mLoaderManager.initLoader(CONVERSATION_MESSAGES_LOADER, args, mMessagesLoaderCallbacks);
- mLoaderManager.initLoader(PARTICIPANT_LOADER, args, mParticipantsLoaderCallbacks);
- mLoaderManager.initLoader(SELF_PARTICIPANT_LOADER, args, mSelfParticipantLoaderCallbacks);
- }
-
- @Override
- protected void unregisterListeners() {
- mListeners.clear();
- // Make sure focus has moved away from this conversation
- // TODO: May false trigger if destroy happens after "new" conversation is focused.
- // Assert.isTrue(!DataModel.get().isFocusedConversation(mConversationId));
-
- // This could be null if we bind but the caller doesn't init the BindableData
- if (mLoaderManager != null) {
- mLoaderManager.destroyLoader(CONVERSATION_META_DATA_LOADER);
- mLoaderManager.destroyLoader(CONVERSATION_MESSAGES_LOADER);
- mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
- mLoaderManager.destroyLoader(SELF_PARTICIPANT_LOADER);
- mLoaderManager = null;
- }
- }
-
- /**
- * Gets the default self participant in the participant table (NOT the conversation's self).
- * This is available as soon as self participant data is loaded.
- */
- public ParticipantData getDefaultSelfParticipant() {
- return mSelfParticipantsData.getDefaultSelfParticipant();
- }
-
- public List<ParticipantData> getSelfParticipants(final boolean activeOnly) {
- return mSelfParticipantsData.getSelfParticipants(activeOnly);
- }
-
- public int getSelfParticipantsCountExcludingDefault(final boolean activeOnly) {
- return mSelfParticipantsData.getSelfParticipantsCountExcludingDefault(activeOnly);
- }
-
- public ParticipantData getSelfParticipantById(final String selfId) {
- return mSelfParticipantsData.getSelfParticipantById(selfId);
- }
-
- /**
- * For a 1:1 conversation return the other (not self) participant (else null)
- */
- public ParticipantData getOtherParticipant() {
- return mParticipantData.getOtherParticipant();
- }
-
- /**
- * Return true once the participants are loaded
- */
- public boolean getParticipantsLoaded() {
- return mParticipantData.isLoaded();
- }
-
- public void sendMessage(final BindingBase<ConversationData> binding,
- final MessageData message) {
- Assert.isTrue(TextUtils.equals(mConversationId, message.getConversationId()));
- Assert.isTrue(binding.getData() == this);
-
- if (!OsUtil.isAtLeastL_MR1() || message.getSelfId() == null) {
- InsertNewMessageAction.insertNewMessage(message);
- } else {
- final int systemDefaultSubId = PhoneUtils.getDefault().getDefaultSmsSubscriptionId();
- if (systemDefaultSubId != ParticipantData.DEFAULT_SELF_SUB_ID &&
- mSelfParticipantsData.isDefaultSelf(message.getSelfId())) {
- // Lock the sub selection to the system default SIM as soon as the user clicks on
- // the send button to avoid races between this and when InsertNewMessageAction is
- // actually executed on the data model thread, during which the user can potentially
- // change the system default SIM in Settings.
- InsertNewMessageAction.insertNewMessage(message, systemDefaultSubId);
- } else {
- InsertNewMessageAction.insertNewMessage(message);
- }
- }
- // Update contacts so Frequents will reflect messaging activity.
- if (!getParticipantsLoaded()) {
- return; // oh well, not critical
- }
- final ArrayList<String> phones = new ArrayList<>();
- final ArrayList<String> emails = new ArrayList<>();
- for (final ParticipantData participant : mParticipantData) {
- if (!participant.isSelf()) {
- if (participant.isEmail()) {
- emails.add(participant.getSendDestination());
- } else {
- phones.add(participant.getSendDestination());
- }
- }
- }
-
- if (ContactUtil.hasReadContactsPermission()) {
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- final DataUsageStatUpdater updater = new DataUsageStatUpdater(
- Factory.get().getApplicationContext());
- try {
- if (!phones.isEmpty()) {
- updater.updateWithPhoneNumber(phones);
- }
- if (!emails.isEmpty()) {
- updater.updateWithAddress(emails);
- }
- } catch (final SQLiteFullException ex) {
- LogUtil.w(TAG, "Unable to update contact", ex);
- }
- }
- });
- }
- }
-
- public void downloadMessage(final BindingBase<ConversationData> binding,
- final String messageId) {
- Assert.isTrue(binding.getData() == this);
- Assert.notNull(messageId);
- RedownloadMmsAction.redownloadMessage(messageId);
- }
-
- public void resendMessage(final BindingBase<ConversationData> binding, final String messageId) {
- Assert.isTrue(binding.getData() == this);
- Assert.notNull(messageId);
- ResendMessageAction.resendMessage(messageId);
- }
-
- public void deleteMessage(final BindingBase<ConversationData> binding, final String messageId) {
- Assert.isTrue(binding.getData() == this);
- Assert.notNull(messageId);
- DeleteMessageAction.deleteMessage(messageId);
- }
-
- public void deleteConversation(final Binding<ConversationData> binding) {
- Assert.isTrue(binding.getData() == this);
- // If possible use timestamp of last message shown to delete only messages user is aware of
- if (mConversationMetadata == null) {
- DeleteConversationAction.deleteConversation(mConversationId,
- System.currentTimeMillis());
- } else {
- mConversationMetadata.deleteConversation();
- }
- }
-
- public void archiveConversation(final BindingBase<ConversationData> binding) {
- Assert.isTrue(binding.getData() == this);
- UpdateConversationArchiveStatusAction.archiveConversation(mConversationId);
- }
-
- public void unarchiveConversation(final BindingBase<ConversationData> binding) {
- Assert.isTrue(binding.getData() == this);
- UpdateConversationArchiveStatusAction.unarchiveConversation(mConversationId);
- }
-
- public ConversationParticipantsData getParticipants() {
- return mParticipantData;
- }
-
- /**
- * Returns a dialable phone number for the participant if we are in a 1-1 conversation.
- * @return the participant phone number, or null if the phone number is not valid or if there
- * are more than one participant.
- */
- public String getParticipantPhoneNumber() {
- final ParticipantData participant = this.getOtherParticipant();
- if (participant != null) {
- final String phoneNumber = participant.getSendDestination();
- if (!TextUtils.isEmpty(phoneNumber) && MmsSmsUtils.isPhoneNumber(phoneNumber)) {
- return phoneNumber;
- }
- }
- return null;
- }
-
- /**
- * Create a message to be forwarded from an existing message.
- */
- public MessageData createForwardedMessage(final ConversationMessageData message) {
- final MessageData forwardedMessage = new MessageData();
-
- final String originalSubject =
- MmsUtils.cleanseMmsSubject(mContext.getResources(), message.getMmsSubject());
- if (!TextUtils.isEmpty(originalSubject)) {
- forwardedMessage.setMmsSubject(
- mContext.getResources().getString(R.string.message_fwd, originalSubject));
- }
-
- for (final MessagePartData part : message.getParts()) {
- MessagePartData forwardedPart;
-
- // Depending on the part type, if it is text, we can directly create a text part;
- // if it is attachment, then we need to create a pending attachment data out of it, so
- // that we may persist the attachment locally in the scratch folder when the user picks
- // a conversation to forward to.
- if (part.isText()) {
- forwardedPart = MessagePartData.createTextMessagePart(part.getText());
- } else {
- final PendingAttachmentData pendingAttachmentData = PendingAttachmentData
- .createPendingAttachmentData(part.getContentType(), part.getContentUri());
- forwardedPart = pendingAttachmentData;
- }
- forwardedMessage.addPart(forwardedPart);
- }
- return forwardedMessage;
- }
-
- public int getNumberOfParticipantsExcludingSelf() {
- return mParticipantData.getNumberOfParticipantsExcludingSelf();
- }
-
- /**
- * Returns {@link com.android.messaging.datamodel.data.SubscriptionListData
- * .SubscriptionListEntry} for a given self participant so UI can display SIM-related info
- * (icon, name etc.) for multi-SIM.
- */
- public SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
- final String selfParticipantId, final boolean excludeDefault) {
- return getSubscriptionEntryForSelfParticipant(selfParticipantId, excludeDefault,
- mSubscriptionListData, mSelfParticipantsData);
- }
-
- /**
- * Returns {@link com.android.messaging.datamodel.data.SubscriptionListData
- * .SubscriptionListEntry} for a given self participant so UI can display SIM-related info
- * (icon, name etc.) for multi-SIM.
- */
- public static SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
- final String selfParticipantId, final boolean excludeDefault,
- final SubscriptionListData subscriptionListData,
- final SelfParticipantsData selfParticipantsData) {
- // SIM indicators are shown in the UI only if:
- // 1. Framework has MSIM support AND
- // 2. The device has had multiple *active* subscriptions. AND
- // 3. The message's subscription is active.
- if (OsUtil.isAtLeastL_MR1() &&
- selfParticipantsData.getSelfParticipantsCountExcludingDefault(true) > 1) {
- return subscriptionListData.getActiveSubscriptionEntryBySelfId(selfParticipantId,
- excludeDefault);
- }
- return null;
- }
-
- public SubscriptionListData getSubscriptionListData() {
- return mSubscriptionListData;
- }
-
- /**
- * A dummy implementation of {@link ConversationDataListener} so that subclasses may opt to
- * implement some, but not all, of the interface methods.
- */
- public static class SimpleConversationDataListener implements ConversationDataListener {
-
- @Override
- public void onConversationMessagesCursorUpdated(final ConversationData data, final Cursor cursor,
- @Nullable
- final
- ConversationMessageData newestMessage, final boolean isSync) {}
-
- @Override
- public void onConversationMetadataUpdated(final ConversationData data) {}
-
- @Override
- public void closeConversation(final String conversationId) {}
-
- @Override
- public void onConversationParticipantDataLoaded(final ConversationData data) {}
-
- @Override
- public void onSubscriptionListDataLoaded(final ConversationData data) {}
-
- }
-
- private class ConversationDataEventDispatcher
- extends ArrayList<ConversationDataListener>
- implements ConversationDataListener {
-
- @Override
- public void onConversationMessagesCursorUpdated(final ConversationData data, final Cursor cursor,
- @Nullable
- final ConversationMessageData newestMessage, final boolean isSync) {
- for (final ConversationDataListener listener : this) {
- listener.onConversationMessagesCursorUpdated(data, cursor, newestMessage, isSync);
- }
- }
-
- @Override
- public void onConversationMetadataUpdated(final ConversationData data) {
- for (final ConversationDataListener listener : this) {
- listener.onConversationMetadataUpdated(data);
- }
- }
-
- @Override
- public void closeConversation(final String conversationId) {
- for (final ConversationDataListener listener : this) {
- listener.closeConversation(conversationId);
- }
- }
-
- @Override
- public void onConversationParticipantDataLoaded(final ConversationData data) {
- for (final ConversationDataListener listener : this) {
- listener.onConversationParticipantDataLoaded(data);
- }
- }
-
- @Override
- public void onSubscriptionListDataLoaded(final ConversationData data) {
- for (final ConversationDataListener listener : this) {
- listener.onSubscriptionListDataLoaded(data);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/ConversationListData.java b/src/com/android/messaging/datamodel/data/ConversationListData.java
deleted file mode 100644
index 3d27ecd..0000000
--- a/src/com/android/messaging/datamodel/data/ConversationListData.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.app.LoaderManager;
-import android.content.Context;
-import android.content.Loader;
-import android.database.Cursor;
-import android.os.Bundle;
-
-import com.android.messaging.datamodel.BoundCursorLoader;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns;
-import com.android.messaging.receiver.SmsReceiver;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.HashSet;
-
-public class ConversationListData extends BindableData
- implements LoaderManager.LoaderCallbacks<Cursor> {
-
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- private static final String BINDING_ID = "bindingId";
- public static final String SORT_ORDER =
- ConversationListViewColumns.SORT_TIMESTAMP + " DESC";
-
- private static final String WHERE_ARCHIVED =
- "(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 1)";
- public static final String WHERE_NOT_ARCHIVED =
- "(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 0)";
-
- public interface ConversationListDataListener {
- public void onConversationListCursorUpdated(ConversationListData data, Cursor cursor);
- public void setBlockedParticipantsAvailable(boolean blockedAvailable);
- }
-
- private ConversationListDataListener mListener;
- private final Context mContext;
- private final boolean mArchivedMode;
- private LoaderManager mLoaderManager;
-
- public ConversationListData(final Context context, final ConversationListDataListener listener,
- final boolean archivedMode) {
- mListener = listener;
- mContext = context;
- mArchivedMode = archivedMode;
- }
-
- private static final int CONVERSATION_LIST_LOADER = 1;
- private static final int BLOCKED_PARTICIPANTS_AVAILABLE_LOADER = 2;
-
- private static final String[] BLOCKED_PARTICIPANTS_PROJECTION = new String[] {
- ParticipantColumns._ID,
- ParticipantColumns.NORMALIZED_DESTINATION,
- };
- private static final int INDEX_BLOCKED_PARTICIPANTS_ID = 0;
- private static final int INDEX_BLOCKED_PARTICIPANTS_NORMALIZED_DESTINATION = 1;
-
- // all blocked participants
- private final HashSet<String> mBlockedParticipants = new HashSet<String>();
-
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- final String bindingId = args.getString(BINDING_ID);
- Loader<Cursor> loader = null;
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- switch (id) {
- case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
- loader = new BoundCursorLoader(bindingId, mContext,
- MessagingContentProvider.PARTICIPANTS_URI,
- BLOCKED_PARTICIPANTS_PROJECTION,
- ParticipantColumns.BLOCKED + "=1", null, null);
- break;
- case CONVERSATION_LIST_LOADER:
- loader = new BoundCursorLoader(bindingId, mContext,
- MessagingContentProvider.CONVERSATIONS_URI,
- ConversationListItemData.PROJECTION,
- mArchivedMode ? WHERE_ARCHIVED : WHERE_NOT_ARCHIVED,
- null, // selection args
- SORT_ORDER);
- break;
- default:
- Assert.fail("Unknown loader id");
- break;
- }
- } else {
- LogUtil.w(TAG, "Creating loader after unbinding list");
- }
- return loader;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
- if (isBound(loader.getBindingId())) {
- switch (loader.getId()) {
- case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
- mBlockedParticipants.clear();
- for (int i = 0; i < data.getCount(); i++) {
- data.moveToPosition(i);
- mBlockedParticipants.add(data.getString(
- INDEX_BLOCKED_PARTICIPANTS_NORMALIZED_DESTINATION));
- }
- mListener.setBlockedParticipantsAvailable(data != null && data.getCount() > 0);
- break;
- case CONVERSATION_LIST_LOADER:
- mListener.onConversationListCursorUpdated(this, data);
- break;
- default:
- Assert.fail("Unknown loader id");
- break;
- }
- } else {
- LogUtil.w(TAG, "Loader finished after unbinding list");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoaderReset(final Loader<Cursor> generic) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
- if (isBound(loader.getBindingId())) {
- switch (loader.getId()) {
- case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
- mListener.setBlockedParticipantsAvailable(false);
- break;
- case CONVERSATION_LIST_LOADER:
- mListener.onConversationListCursorUpdated(this, null);
- break;
- default:
- Assert.fail("Unknown loader id");
- break;
- }
- } else {
- LogUtil.w(TAG, "Loader reset after unbinding list");
- }
- }
-
- private Bundle mArgs;
-
- public void init(final LoaderManager loaderManager,
- final BindingBase<ConversationListData> binding) {
- mArgs = new Bundle();
- mArgs.putString(BINDING_ID, binding.getBindingId());
- mLoaderManager = loaderManager;
- mLoaderManager.initLoader(CONVERSATION_LIST_LOADER, mArgs, this);
- mLoaderManager.initLoader(BLOCKED_PARTICIPANTS_AVAILABLE_LOADER, mArgs, this);
- }
-
- public void handleMessagesSeen() {
- BugleNotifications.markAllMessagesAsSeen();
-
- SmsReceiver.cancelSecondaryUserNotification();
- }
-
- @Override
- protected void unregisterListeners() {
- mListener = null;
-
- // This could be null if we bind but the caller doesn't init the BindableData
- if (mLoaderManager != null) {
- mLoaderManager.destroyLoader(CONVERSATION_LIST_LOADER);
- mLoaderManager.destroyLoader(BLOCKED_PARTICIPANTS_AVAILABLE_LOADER);
- mLoaderManager = null;
- }
- }
-
- public boolean getHasFirstSyncCompleted() {
- final SyncManager syncManager = DataModel.get().getSyncManager();
- return syncManager.getHasFirstSyncCompleted();
- }
-
- public void setScrolledToNewestConversation(boolean scrolledToNewestConversation) {
- DataModel.get().setConversationListScrolledToNewestConversation(
- scrolledToNewestConversation);
- if (scrolledToNewestConversation) {
- handleMessagesSeen();
- }
- }
-
- public HashSet<String> getBlockedParticipants() {
- return mBlockedParticipants;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/ConversationListItemData.java b/src/com/android/messaging/datamodel/data/ConversationListItemData.java
deleted file mode 100644
index b2e6e1c..0000000
--- a/src/com/android/messaging/datamodel/data/ConversationListItemData.java
+++ /dev/null
@@ -1,510 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.BaseColumns;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.action.DeleteConversationAction;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Dates;
-import com.google.common.base.Joiner;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class wrapping the conversation list view used to display each item in conversation list
- */
-public class ConversationListItemData {
- private String mConversationId;
- private String mName;
- private String mIcon;
- private boolean mIsRead;
- private long mTimestamp;
- private String mSnippetText;
- private Uri mPreviewUri;
- private String mPreviewContentType;
- private long mParticipantContactId;
- private String mParticipantLookupKey;
- private String mOtherParticipantNormalizedDestination;
- private String mSelfId;
- private int mParticipantCount;
- private boolean mNotificationEnabled;
- private String mNotificationSoundUri;
- private boolean mNotificationVibrate;
- private boolean mIncludeEmailAddress;
- private int mMessageStatus;
- private int mMessageRawTelephonyStatus;
- private boolean mShowDraft;
- private Uri mDraftPreviewUri;
- private String mDraftPreviewContentType;
- private String mDraftSnippetText;
- private boolean mIsArchived;
- private String mSubject;
- private String mDraftSubject;
- private String mSnippetSenderFirstName;
- private String mSnippetSenderDisplayDestination;
-
- public ConversationListItemData() {
- }
-
- public void bind(final Cursor cursor) {
- bind(cursor, false);
- }
-
- public void bind(final Cursor cursor, final boolean ignoreDraft) {
- mConversationId = cursor.getString(INDEX_ID);
- mName = cursor.getString(INDEX_CONVERSATION_NAME);
- mIcon = cursor.getString(INDEX_CONVERSATION_ICON);
- mSnippetText = cursor.getString(INDEX_SNIPPET_TEXT);
- mTimestamp = cursor.getLong(INDEX_SORT_TIMESTAMP);
- mIsRead = cursor.getInt(INDEX_READ) == 1;
- final String previewUriString = cursor.getString(INDEX_PREVIEW_URI);
- mPreviewUri = TextUtils.isEmpty(previewUriString) ? null : Uri.parse(previewUriString);
- mPreviewContentType = cursor.getString(INDEX_PREVIEW_CONTENT_TYPE);
- mParticipantContactId = cursor.getLong(INDEX_PARTICIPANT_CONTACT_ID);
- mParticipantLookupKey = cursor.getString(INDEX_PARTICIPANT_LOOKUP_KEY);
- mOtherParticipantNormalizedDestination = cursor.getString(
- INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION);
- mSelfId = cursor.getString(INDEX_SELF_ID);
- mParticipantCount = cursor.getInt(INDEX_PARTICIPANT_COUNT);
- mNotificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1;
- mNotificationSoundUri = cursor.getString(INDEX_NOTIFICATION_SOUND_URI);
- mNotificationVibrate = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1;
- mIncludeEmailAddress = cursor.getInt(INDEX_INCLUDE_EMAIL_ADDRESS) == 1;
- mMessageStatus = cursor.getInt(INDEX_MESSAGE_STATUS);
- mMessageRawTelephonyStatus = cursor.getInt(INDEX_MESSAGE_RAW_TELEPHONY_STATUS);
- if (!ignoreDraft) {
- mShowDraft = cursor.getInt(INDEX_SHOW_DRAFT) == 1;
- final String draftPreviewUriString = cursor.getString(INDEX_DRAFT_PREVIEW_URI);
- mDraftPreviewUri = TextUtils.isEmpty(draftPreviewUriString) ?
- null : Uri.parse(draftPreviewUriString);
- mDraftPreviewContentType = cursor.getString(INDEX_DRAFT_PREVIEW_CONTENT_TYPE);
- mDraftSnippetText = cursor.getString(INDEX_DRAFT_SNIPPET_TEXT);
- mDraftSubject = cursor.getString(INDEX_DRAFT_SUBJECT_TEXT);
- } else {
- mShowDraft = false;
- mDraftPreviewUri = null;
- mDraftPreviewContentType = null;
- mDraftSnippetText = null;
- mDraftSubject = null;
- }
-
- mIsArchived = cursor.getInt(INDEX_ARCHIVE_STATUS) == 1;
- mSubject = cursor.getString(INDEX_SUBJECT_TEXT);
- mSnippetSenderFirstName = cursor.getString(INDEX_SNIPPET_SENDER_FIRST_NAME);
- mSnippetSenderDisplayDestination =
- cursor.getString(INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION);
- }
-
- public String getConversationId() {
- return mConversationId;
- }
-
- public String getName() {
- return mName;
- }
-
- public String getIcon() {
- return mIcon;
- }
-
- public boolean getIsRead() {
- return mIsRead;
- }
-
- public String getFormattedTimestamp() {
- return Dates.getConversationTimeString(mTimestamp).toString();
- }
-
- public long getTimestamp() {
- return mTimestamp;
- }
-
- public String getSnippetText() {
- return mSnippetText;
- }
-
- public Uri getPreviewUri() {
- return mPreviewUri;
- }
-
- public String getPreviewContentType() {
- return mPreviewContentType;
- }
-
- public long getParticipantContactId() {
- return mParticipantContactId;
- }
-
- public String getParticipantLookupKey() {
- return mParticipantLookupKey;
- }
-
- public String getOtherParticipantNormalizedDestination() {
- return mOtherParticipantNormalizedDestination;
- }
-
- public String getSelfId() {
- return mSelfId;
- }
-
- public int getParticipantCount() {
- return mParticipantCount;
- }
-
- public boolean getIsGroup() {
- // Participant count excludes self
- return (mParticipantCount > 1);
- }
-
- public boolean getIncludeEmailAddress() {
- return mIncludeEmailAddress;
- }
-
- public boolean getNotificationEnabled() {
- return mNotificationEnabled;
- }
-
- public String getNotificationSoundUri() {
- return mNotificationSoundUri;
- }
-
- public boolean getNotifiationVibrate() {
- return mNotificationVibrate;
- }
-
- public final boolean getIsFailedStatus() {
- return (mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_FAILED ||
- mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER ||
- mMessageStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
- mMessageStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE);
- }
-
- public final boolean getIsSendRequested() {
- return (mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND ||
- mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY ||
- mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_SENDING ||
- mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_RESENDING);
- }
-
- public boolean getIsMessageTypeOutgoing() {
- return !MessageData.getIsIncoming(mMessageStatus);
- }
-
- public int getMessageRawTelephonyStatus() {
- return mMessageRawTelephonyStatus;
- }
-
- public int getMessageStatus() {
- return mMessageStatus;
- }
-
- public boolean getShowDraft() {
- return mShowDraft;
- }
-
- public String getDraftSnippetText() {
- return mDraftSnippetText;
- }
-
- public Uri getDraftPreviewUri() {
- return mDraftPreviewUri;
- }
-
- public String getDraftPreviewContentType() {
- return mDraftPreviewContentType;
- }
-
- public boolean getIsArchived() {
- return mIsArchived;
- }
-
- public String getSubject() {
- return mSubject;
- }
-
- public String getDraftSubject() {
- return mDraftSubject;
- }
-
- public String getSnippetSenderName() {
- if (!TextUtils.isEmpty(mSnippetSenderFirstName)) {
- return mSnippetSenderFirstName;
- }
- return mSnippetSenderDisplayDestination;
- }
-
- public void deleteConversation() {
- DeleteConversationAction.deleteConversation(mConversationId, mTimestamp);
- }
-
- /**
- * Get the name of the view for this data item
- */
- public static final String getConversationListView() {
- return CONVERSATION_LIST_VIEW;
- }
-
- public static final String getConversationListViewSql() {
- return CONVERSATION_LIST_VIEW_SQL;
- }
-
- private static final String CONVERSATION_LIST_VIEW = "conversation_list_view";
-
- private static final String CONVERSATION_LIST_VIEW_PROJECTION =
- DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns._ID
- + " as " + ConversationListViewColumns._ID + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NAME
- + " as " + ConversationListViewColumns.NAME + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.CURRENT_SELF_ID
- + " as " + ConversationListViewColumns.CURRENT_SELF_ID + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ARCHIVE_STATUS
- + " as " + ConversationListViewColumns.ARCHIVE_STATUS + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ
- + " as " + ConversationListViewColumns.READ + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ICON
- + " as " + ConversationListViewColumns.ICON + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_CONTACT_ID
- + " as " + ConversationListViewColumns.PARTICIPANT_CONTACT_ID + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_LOOKUP_KEY
- + " as " + ConversationListViewColumns.PARTICIPANT_LOOKUP_KEY + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.'
- + ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION
- + " as " + ConversationListViewColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SORT_TIMESTAMP
- + " as " + ConversationListViewColumns.SORT_TIMESTAMP + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SHOW_DRAFT
- + " as " + ConversationListViewColumns.SHOW_DRAFT + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_SNIPPET_TEXT
- + " as " + ConversationListViewColumns.DRAFT_SNIPPET_TEXT + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_PREVIEW_URI
- + " as " + ConversationListViewColumns.DRAFT_PREVIEW_URI + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_SUBJECT_TEXT
- + " as " + ConversationListViewColumns.DRAFT_SUBJECT_TEXT + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.'
- + ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE
- + " as " + ConversationListViewColumns.DRAFT_PREVIEW_CONTENT_TYPE + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PREVIEW_URI
- + " as " + ConversationListViewColumns.PREVIEW_URI + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PREVIEW_CONTENT_TYPE
- + " as " + ConversationListViewColumns.PREVIEW_CONTENT_TYPE + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_COUNT
- + " as " + ConversationListViewColumns.PARTICIPANT_COUNT + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_ENABLED
- + " as " + ConversationListViewColumns.NOTIFICATION_ENABLED + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_SOUND_URI
- + " as " + ConversationListViewColumns.NOTIFICATION_SOUND_URI + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_VIBRATION
- + " as " + ConversationListViewColumns.NOTIFICATION_VIBRATION + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' +
- ConversationColumns.INCLUDE_EMAIL_ADDRESS
- + " as " + ConversationListViewColumns.INCLUDE_EMAIL_ADDRESS + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
- + " as " + ConversationListViewColumns.MESSAGE_STATUS + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS
- + " as " + ConversationListViewColumns.MESSAGE_RAW_TELEPHONY_STATUS + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
- + " as " + ConversationListViewColumns.MESSAGE_ID + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME
- + " as " + ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
- + " as " + ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION;
-
- private static final String JOIN_PARTICIPANTS =
- " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE + " ON ("
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
- + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + DatabaseHelper.ParticipantColumns._ID
- + ") ";
-
- // View that makes latest message read flag available with rest of conversation data.
- private static final String CONVERSATION_LIST_VIEW_SQL = "CREATE VIEW " +
- CONVERSATION_LIST_VIEW + " AS SELECT "
- + CONVERSATION_LIST_VIEW_PROJECTION + ", "
- // Snippet not part of the base projection shared with search view
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SNIPPET_TEXT
- + " as " + ConversationListViewColumns.SNIPPET_TEXT + ", "
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SUBJECT_TEXT
- + " as " + ConversationListViewColumns.SUBJECT_TEXT + " "
- + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
- + " LEFT JOIN " + DatabaseHelper.MESSAGES_TABLE + " ON ("
- + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.LATEST_MESSAGE_ID
- + '=' + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID + ") "
- + JOIN_PARTICIPANTS
- + "ORDER BY " + DatabaseHelper.CONVERSATIONS_TABLE + '.'
- + ConversationColumns.SORT_TIMESTAMP + " DESC";
-
- public static class ConversationListViewColumns implements BaseColumns {
- public static final String _ID = ConversationColumns._ID;
- static final String NAME = ConversationColumns.NAME;
- static final String ARCHIVE_STATUS = ConversationColumns.ARCHIVE_STATUS;
- static final String READ = MessageColumns.READ;
- static final String SORT_TIMESTAMP = ConversationColumns.SORT_TIMESTAMP;
- static final String PREVIEW_URI = ConversationColumns.PREVIEW_URI;
- static final String PREVIEW_CONTENT_TYPE = ConversationColumns.PREVIEW_CONTENT_TYPE;
- static final String SNIPPET_TEXT = ConversationColumns.SNIPPET_TEXT;
- static final String SUBJECT_TEXT = ConversationColumns.SUBJECT_TEXT;
- static final String ICON = ConversationColumns.ICON;
- static final String SHOW_DRAFT = ConversationColumns.SHOW_DRAFT;
- static final String DRAFT_SUBJECT_TEXT = ConversationColumns.DRAFT_SUBJECT_TEXT;
- static final String DRAFT_PREVIEW_URI = ConversationColumns.DRAFT_PREVIEW_URI;
- static final String DRAFT_PREVIEW_CONTENT_TYPE =
- ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE;
- static final String DRAFT_SNIPPET_TEXT = ConversationColumns.DRAFT_SNIPPET_TEXT;
- static final String PARTICIPANT_CONTACT_ID = ConversationColumns.PARTICIPANT_CONTACT_ID;
- static final String PARTICIPANT_LOOKUP_KEY = ConversationColumns.PARTICIPANT_LOOKUP_KEY;
- static final String OTHER_PARTICIPANT_NORMALIZED_DESTINATION =
- ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION;
- static final String CURRENT_SELF_ID = ConversationColumns.CURRENT_SELF_ID;
- static final String PARTICIPANT_COUNT = ConversationColumns.PARTICIPANT_COUNT;
- static final String NOTIFICATION_ENABLED = ConversationColumns.NOTIFICATION_ENABLED;
- static final String NOTIFICATION_SOUND_URI = ConversationColumns.NOTIFICATION_SOUND_URI;
- static final String NOTIFICATION_VIBRATION = ConversationColumns.NOTIFICATION_VIBRATION;
- static final String INCLUDE_EMAIL_ADDRESS =
- ConversationColumns.INCLUDE_EMAIL_ADDRESS;
- static final String MESSAGE_STATUS = MessageColumns.STATUS;
- static final String MESSAGE_RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS;
- static final String MESSAGE_ID = "message_id";
- static final String SNIPPET_SENDER_FIRST_NAME = "snippet_sender_first_name";
- static final String SNIPPET_SENDER_DISPLAY_DESTINATION =
- "snippet_sender_display_destination";
- }
-
- public static final String[] PROJECTION = {
- ConversationListViewColumns._ID,
- ConversationListViewColumns.NAME,
- ConversationListViewColumns.ICON,
- ConversationListViewColumns.SNIPPET_TEXT,
- ConversationListViewColumns.SORT_TIMESTAMP,
- ConversationListViewColumns.READ,
- ConversationListViewColumns.PREVIEW_URI,
- ConversationListViewColumns.PREVIEW_CONTENT_TYPE,
- ConversationListViewColumns.PARTICIPANT_CONTACT_ID,
- ConversationListViewColumns.PARTICIPANT_LOOKUP_KEY,
- ConversationListViewColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION,
- ConversationListViewColumns.PARTICIPANT_COUNT,
- ConversationListViewColumns.CURRENT_SELF_ID,
- ConversationListViewColumns.NOTIFICATION_ENABLED,
- ConversationListViewColumns.NOTIFICATION_SOUND_URI,
- ConversationListViewColumns.NOTIFICATION_VIBRATION,
- ConversationListViewColumns.INCLUDE_EMAIL_ADDRESS,
- ConversationListViewColumns.MESSAGE_STATUS,
- ConversationListViewColumns.SHOW_DRAFT,
- ConversationListViewColumns.DRAFT_PREVIEW_URI,
- ConversationListViewColumns.DRAFT_PREVIEW_CONTENT_TYPE,
- ConversationListViewColumns.DRAFT_SNIPPET_TEXT,
- ConversationListViewColumns.ARCHIVE_STATUS,
- ConversationListViewColumns.MESSAGE_ID,
- ConversationListViewColumns.SUBJECT_TEXT,
- ConversationListViewColumns.DRAFT_SUBJECT_TEXT,
- ConversationListViewColumns.MESSAGE_RAW_TELEPHONY_STATUS,
- ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME,
- ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION,
- };
-
- private static final int INDEX_ID = 0;
- private static final int INDEX_CONVERSATION_NAME = 1;
- private static final int INDEX_CONVERSATION_ICON = 2;
- private static final int INDEX_SNIPPET_TEXT = 3;
- private static final int INDEX_SORT_TIMESTAMP = 4;
- private static final int INDEX_READ = 5;
- private static final int INDEX_PREVIEW_URI = 6;
- private static final int INDEX_PREVIEW_CONTENT_TYPE = 7;
- private static final int INDEX_PARTICIPANT_CONTACT_ID = 8;
- private static final int INDEX_PARTICIPANT_LOOKUP_KEY = 9;
- private static final int INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION = 10;
- private static final int INDEX_PARTICIPANT_COUNT = 11;
- private static final int INDEX_SELF_ID = 12;
- private static final int INDEX_NOTIFICATION_ENABLED = 13;
- private static final int INDEX_NOTIFICATION_SOUND_URI = 14;
- private static final int INDEX_NOTIFICATION_VIBRATION = 15;
- private static final int INDEX_INCLUDE_EMAIL_ADDRESS = 16;
- private static final int INDEX_MESSAGE_STATUS = 17;
- private static final int INDEX_SHOW_DRAFT = 18;
- private static final int INDEX_DRAFT_PREVIEW_URI = 19;
- private static final int INDEX_DRAFT_PREVIEW_CONTENT_TYPE = 20;
- private static final int INDEX_DRAFT_SNIPPET_TEXT = 21;
- private static final int INDEX_ARCHIVE_STATUS = 22;
- private static final int INDEX_MESSAGE_ID = 23;
- private static final int INDEX_SUBJECT_TEXT = 24;
- private static final int INDEX_DRAFT_SUBJECT_TEXT = 25;
- private static final int INDEX_MESSAGE_RAW_TELEPHONY_STATUS = 26;
- private static final int INDEX_SNIPPET_SENDER_FIRST_NAME = 27;
- private static final int INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION = 28;
-
- private static final String DIVIDER_TEXT = ", ";
-
- /**
- * Get a conversation from the local DB based on the conversation id.
- *
- * @param dbWrapper The database
- * @param conversationId The conversation Id to read
- * @return The existing conversation or null
- */
- public static ConversationListItemData getExistingConversation(final DatabaseWrapper dbWrapper,
- final String conversationId) {
- ConversationListItemData conversation = null;
-
- // Look for an existing conversation in the db with this conversation id
- Cursor cursor = null;
- try {
- // TODO: Should we be able to read a row from just the conversation table?
- cursor = dbWrapper.query(getConversationListView(),
- PROJECTION,
- ConversationColumns._ID + "=?",
- new String[] { conversationId },
- null, null, null);
- Assert.inRange(cursor.getCount(), 0, 1);
- if (cursor.moveToFirst()) {
- conversation = new ConversationListItemData();
- conversation.bind(cursor);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return conversation;
- }
-
- public static String generateConversationName(final List<ParticipantData>
- participants) {
- if (participants.size() == 1) {
- // Prefer full name over first name for 1:1 conversation
- return participants.get(0).getDisplayName(true);
- }
-
- final ArrayList<String> participantNames = new ArrayList<String>();
- for (final ParticipantData participant : participants) {
- // Prefer first name over full name for group conversation
- participantNames.add(participant.getDisplayName(false));
- }
-
- final Joiner joiner = Joiner.on(DIVIDER_TEXT).skipNulls();
- return joiner.join(participantNames);
- }
-
-}
diff --git a/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java b/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java
deleted file mode 100644
index f329f46..0000000
--- a/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.data;
-
-import android.text.TextUtils;
-
-/**
- * Holds data for conversation message bubble which keeps track of whether it's been bound to
- * a new message.
- */
-public class ConversationMessageBubbleData {
- private String mMessageId;
-
- /**
- * Binds to ConversationMessageData instance.
- * @return true if we are binding to a different message, false if we are binding to the
- * same message (e.g. in order to update the status text)
- */
- public boolean bind(final ConversationMessageData data) {
- final boolean changed = !TextUtils.equals(mMessageId, data.getMessageId());
- mMessageId = data.getMessageId();
- return changed;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/ConversationMessageData.java b/src/com/android/messaging/datamodel/data/ConversationMessageData.java
deleted file mode 100644
index 19e1b97..0000000
--- a/src/com/android/messaging/datamodel/data/ConversationMessageData.java
+++ /dev/null
@@ -1,917 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.data;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.BaseColumns;
-import android.provider.ContactsContract;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.LogUtil;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Predicate;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Class representing a message within a conversation sequence. The message parts
- * are available via the getParts() method.
- *
- * TODO: See if we can delegate to MessageData for the logic that this class duplicates
- * (e.g. getIsMms).
- */
-public class ConversationMessageData {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private String mMessageId;
- private String mConversationId;
- private String mParticipantId;
- private int mPartsCount;
- private List<MessagePartData> mParts;
- private long mSentTimestamp;
- private long mReceivedTimestamp;
- private boolean mSeen;
- private boolean mRead;
- private int mProtocol;
- private int mStatus;
- private String mSmsMessageUri;
- private int mSmsPriority;
- private int mSmsMessageSize;
- private String mMmsSubject;
- private long mMmsExpiry;
- private int mRawTelephonyStatus;
- private String mSenderFullName;
- private String mSenderFirstName;
- private String mSenderDisplayDestination;
- private String mSenderNormalizedDestination;
- private String mSenderProfilePhotoUri;
- private long mSenderContactId;
- private String mSenderContactLookupKey;
- private String mSelfParticipantId;
-
- /** Are we similar enough to the previous/next messages that we can cluster them? */
- private boolean mCanClusterWithPreviousMessage;
- private boolean mCanClusterWithNextMessage;
-
- public ConversationMessageData() {
- }
-
- public void bind(final Cursor cursor) {
- mMessageId = cursor.getString(INDEX_MESSAGE_ID);
- mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
- mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
- mPartsCount = cursor.getInt(INDEX_PARTS_COUNT);
-
- mParts = makeParts(
- cursor.getString(INDEX_PARTS_IDS),
- cursor.getString(INDEX_PARTS_CONTENT_TYPES),
- cursor.getString(INDEX_PARTS_CONTENT_URIS),
- cursor.getString(INDEX_PARTS_WIDTHS),
- cursor.getString(INDEX_PARTS_HEIGHTS),
- cursor.getString(INDEX_PARTS_TEXTS),
- mPartsCount,
- mMessageId);
-
- mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP);
- mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
- mSeen = (cursor.getInt(INDEX_SEEN) != 0);
- mRead = (cursor.getInt(INDEX_READ) != 0);
- mProtocol = cursor.getInt(INDEX_PROTOCOL);
- mStatus = cursor.getInt(INDEX_STATUS);
- mSmsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
- mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
- mSmsMessageSize = cursor.getInt(INDEX_SMS_MESSAGE_SIZE);
- mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
- mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
- mRawTelephonyStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
- mSenderFullName = cursor.getString(INDEX_SENDER_FULL_NAME);
- mSenderFirstName = cursor.getString(INDEX_SENDER_FIRST_NAME);
- mSenderDisplayDestination = cursor.getString(INDEX_SENDER_DISPLAY_DESTINATION);
- mSenderNormalizedDestination = cursor.getString(INDEX_SENDER_NORMALIZED_DESTINATION);
- mSenderProfilePhotoUri = cursor.getString(INDEX_SENDER_PROFILE_PHOTO_URI);
- mSenderContactId = cursor.getLong(INDEX_SENDER_CONTACT_ID);
- mSenderContactLookupKey = cursor.getString(INDEX_SENDER_CONTACT_LOOKUP_KEY);
- mSelfParticipantId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID);
-
- if (!cursor.isFirst() && cursor.moveToPrevious()) {
- mCanClusterWithPreviousMessage = canClusterWithMessage(cursor);
- cursor.moveToNext();
- } else {
- mCanClusterWithPreviousMessage = false;
- }
- if (!cursor.isLast() && cursor.moveToNext()) {
- mCanClusterWithNextMessage = canClusterWithMessage(cursor);
- cursor.moveToPrevious();
- } else {
- mCanClusterWithNextMessage = false;
- }
- }
-
- private boolean canClusterWithMessage(final Cursor cursor) {
- final String otherParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
- if (!TextUtils.equals(getParticipantId(), otherParticipantId)) {
- return false;
- }
- final int otherStatus = cursor.getInt(INDEX_STATUS);
- final boolean otherIsIncoming = (otherStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
- if (getIsIncoming() != otherIsIncoming) {
- return false;
- }
- final long otherReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
- final long timestampDeltaMillis = Math.abs(mReceivedTimestamp - otherReceivedTimestamp);
- if (timestampDeltaMillis > DateUtils.MINUTE_IN_MILLIS) {
- return false;
- }
- final String otherSelfId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID);
- if (!TextUtils.equals(getSelfParticipantId(), otherSelfId)) {
- return false;
- }
- return true;
- }
-
- private static final Character QUOTE_CHAR = '\'';
- private static final char DIVIDER = '|';
-
- // statics to avoid unnecessary object allocation
- private static final StringBuilder sUnquoteStringBuilder = new StringBuilder();
- private static final ArrayList<String> sUnquoteResults = new ArrayList<String>();
-
- // this lock is used to guard access to the above statics
- private static final Object sUnquoteLock = new Object();
-
- private static void addResult(final ArrayList<String> results, final StringBuilder value) {
- if (value.length() > 0) {
- results.add(value.toString());
- } else {
- results.add(EMPTY_STRING);
- }
- }
-
- @VisibleForTesting
- static String[] splitUnquotedString(final String inputString) {
- if (TextUtils.isEmpty(inputString)) {
- return new String[0];
- }
-
- return inputString.split("\\" + DIVIDER);
- }
-
- /**
- * Takes a group-concated and quoted string and decomposes it into its constituent
- * parts. A quoted string starts and ends with a single quote. Actual single quotes
- * within the string are escaped using a second single quote. So, for example, an
- * input string with 3 constituent parts might look like this:
- *
- * 'now is the time'|'I can''t do it'|'foo'
- *
- * This would be returned as an array of 3 strings as follows:
- * now is the time
- * I can't do it
- * foo
- *
- * This is achieved by walking through the inputString, character by character,
- * ignoring the outer quotes and the divider and replacing any pair of consecutive
- * single quotes with a single single quote.
- *
- * @param inputString
- * @return array of constituent strings
- */
- @VisibleForTesting
- static String[] splitQuotedString(final String inputString) {
- if (TextUtils.isEmpty(inputString)) {
- return new String[0];
- }
-
- // this method can be called from multiple threads but it uses a static
- // string builder
- synchronized (sUnquoteLock) {
- final int length = inputString.length();
- final ArrayList<String> results = sUnquoteResults;
- results.clear();
-
- int characterPos = -1;
- while (++characterPos < length) {
- final char mustBeQuote = inputString.charAt(characterPos);
- Assert.isTrue(QUOTE_CHAR == mustBeQuote);
- while (++characterPos < length) {
- final char currentChar = inputString.charAt(characterPos);
- if (currentChar == QUOTE_CHAR) {
- final char peekAhead = characterPos < length - 1
- ? inputString.charAt(characterPos + 1) : 0;
-
- if (peekAhead == QUOTE_CHAR) {
- characterPos += 1; // skip the second quote
- } else {
- addResult(results, sUnquoteStringBuilder);
- sUnquoteStringBuilder.setLength(0);
-
- Assert.isTrue((peekAhead == DIVIDER) || (peekAhead == (char) 0));
- characterPos += 1; // skip the divider
- break;
- }
- }
- sUnquoteStringBuilder.append(currentChar);
- }
- }
- return results.toArray(new String[results.size()]);
- }
- }
-
- static MessagePartData makePartData(
- final String partId,
- final String contentType,
- final String contentUriString,
- final String contentWidth,
- final String contentHeight,
- final String text,
- final String messageId) {
- if (ContentType.isTextType(contentType)) {
- final MessagePartData textPart = MessagePartData.createTextMessagePart(text);
- textPart.updatePartId(partId);
- textPart.updateMessageId(messageId);
- return textPart;
- } else {
- final Uri contentUri = Uri.parse(contentUriString);
- final int width = Integer.parseInt(contentWidth);
- final int height = Integer.parseInt(contentHeight);
- final MessagePartData attachmentPart = MessagePartData.createMediaMessagePart(
- contentType, contentUri, width, height);
- attachmentPart.updatePartId(partId);
- attachmentPart.updateMessageId(messageId);
- return attachmentPart;
- }
- }
-
- @VisibleForTesting
- static List<MessagePartData> makeParts(
- final String rawIds,
- final String rawContentTypes,
- final String rawContentUris,
- final String rawWidths,
- final String rawHeights,
- final String rawTexts,
- final int partsCount,
- final String messageId) {
- final List<MessagePartData> parts = new LinkedList<MessagePartData>();
- if (partsCount == 1) {
- parts.add(makePartData(
- rawIds,
- rawContentTypes,
- rawContentUris,
- rawWidths,
- rawHeights,
- rawTexts,
- messageId));
- } else {
- unpackMessageParts(
- parts,
- splitUnquotedString(rawIds),
- splitQuotedString(rawContentTypes),
- splitQuotedString(rawContentUris),
- splitUnquotedString(rawWidths),
- splitUnquotedString(rawHeights),
- splitQuotedString(rawTexts),
- partsCount,
- messageId);
- }
- return parts;
- }
-
- @VisibleForTesting
- static void unpackMessageParts(
- final List<MessagePartData> parts,
- final String[] ids,
- final String[] contentTypes,
- final String[] contentUris,
- final String[] contentWidths,
- final String[] contentHeights,
- final String[] texts,
- final int partsCount,
- final String messageId) {
-
- Assert.equals(partsCount, ids.length);
- Assert.equals(partsCount, contentTypes.length);
- Assert.equals(partsCount, contentUris.length);
- Assert.equals(partsCount, contentWidths.length);
- Assert.equals(partsCount, contentHeights.length);
- Assert.equals(partsCount, texts.length);
-
- for (int i = 0; i < partsCount; i++) {
- parts.add(makePartData(
- ids[i],
- contentTypes[i],
- contentUris[i],
- contentWidths[i],
- contentHeights[i],
- texts[i],
- messageId));
- }
-
- if (parts.size() != partsCount) {
- LogUtil.wtf(TAG, "Only unpacked " + parts.size() + " parts from message (id="
- + messageId + "), expected " + partsCount + " parts");
- }
- }
-
- public final String getMessageId() {
- return mMessageId;
- }
-
- public final String getConversationId() {
- return mConversationId;
- }
-
- public final String getParticipantId() {
- return mParticipantId;
- }
-
- public List<MessagePartData> getParts() {
- return mParts;
- }
-
- public boolean hasText() {
- for (final MessagePartData part : mParts) {
- if (part.isText()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Get a concatenation of all text parts
- *
- * @return the text that is a concatenation of all text parts
- */
- public String getText() {
- // This is optimized for single text part case, which is the majority
-
- // For single text part, we just return the part without creating the StringBuilder
- String firstTextPart = null;
- boolean foundText = false;
- // For multiple text parts, we need the StringBuilder and the separator for concatenation
- StringBuilder sb = null;
- String separator = null;
- for (final MessagePartData part : mParts) {
- if (part.isText()) {
- if (!foundText) {
- // First text part
- firstTextPart = part.getText();
- foundText = true;
- } else {
- // Second and beyond
- if (sb == null) {
- // Need the StringBuilder and the separator starting from 2nd text part
- sb = new StringBuilder();
- if (!TextUtils.isEmpty(firstTextPart)) {
- sb.append(firstTextPart);
- }
- separator = BugleGservices.get().getString(
- BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR,
- BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR_DEFAULT);
- }
- final String partText = part.getText();
- if (!TextUtils.isEmpty(partText)) {
- if (!TextUtils.isEmpty(separator) && sb.length() > 0) {
- sb.append(separator);
- }
- sb.append(partText);
- }
- }
- }
- }
- if (sb == null) {
- // Only one text part
- return firstTextPart;
- } else {
- // More than one
- return sb.toString();
- }
- }
-
- public boolean hasAttachments() {
- for (final MessagePartData part : mParts) {
- if (part.isAttachment()) {
- return true;
- }
- }
- return false;
- }
-
- public List<MessagePartData> getAttachments() {
- return getAttachments(null);
- }
-
- public List<MessagePartData> getAttachments(final Predicate<MessagePartData> filter) {
- if (mParts.isEmpty()) {
- return Collections.emptyList();
- }
- final List<MessagePartData> attachmentParts = new LinkedList<>();
- for (final MessagePartData part : mParts) {
- if (part.isAttachment()) {
- if (filter == null || filter.apply(part)) {
- attachmentParts.add(part);
- }
- }
- }
- return attachmentParts;
- }
-
- public final long getSentTimeStamp() {
- return mSentTimestamp;
- }
-
- public final long getReceivedTimeStamp() {
- return mReceivedTimestamp;
- }
-
- public final String getFormattedReceivedTimeStamp() {
- return Dates.getMessageTimeString(mReceivedTimestamp).toString();
- }
-
- public final boolean getIsSeen() {
- return mSeen;
- }
-
- public final boolean getIsRead() {
- return mRead;
- }
-
- public final boolean getIsMms() {
- return (mProtocol == MessageData.PROTOCOL_MMS ||
- mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
- }
-
- public final boolean getIsMmsNotification() {
- return (mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
- }
-
- public final boolean getIsSms() {
- return mProtocol == (MessageData.PROTOCOL_SMS);
- }
-
- final int getProtocol() {
- return mProtocol;
- }
-
- public final int getStatus() {
- return mStatus;
- }
-
- public final String getSmsMessageUri() {
- return mSmsMessageUri;
- }
-
- public final int getSmsPriority() {
- return mSmsPriority;
- }
-
- public final int getSmsMessageSize() {
- return mSmsMessageSize;
- }
-
- public final String getMmsSubject() {
- return mMmsSubject;
- }
-
- public final long getMmsExpiry() {
- return mMmsExpiry;
- }
-
- public final int getRawTelephonyStatus() {
- return mRawTelephonyStatus;
- }
-
- public final String getSelfParticipantId() {
- return mSelfParticipantId;
- }
-
- public boolean getIsIncoming() {
- return (mStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
- }
-
- public boolean hasIncomingErrorStatus() {
- return (mStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE ||
- mStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED);
- }
-
- public boolean getIsSendComplete() {
- return mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
- }
-
- public String getSenderFullName() {
- return mSenderFullName;
- }
-
- public String getSenderFirstName() {
- return mSenderFirstName;
- }
-
- public String getSenderDisplayDestination() {
- return mSenderDisplayDestination;
- }
-
- public String getSenderNormalizedDestination() {
- return mSenderNormalizedDestination;
- }
-
- public Uri getSenderProfilePhotoUri() {
- return mSenderProfilePhotoUri == null ? null : Uri.parse(mSenderProfilePhotoUri);
- }
-
- public long getSenderContactId() {
- return mSenderContactId;
- }
-
- public String getSenderDisplayName() {
- if (!TextUtils.isEmpty(mSenderFullName)) {
- return mSenderFullName;
- }
- if (!TextUtils.isEmpty(mSenderFirstName)) {
- return mSenderFirstName;
- }
- return mSenderDisplayDestination;
- }
-
- public String getSenderContactLookupKey() {
- return mSenderContactLookupKey;
- }
-
- public boolean getShowDownloadMessage() {
- return MessageData.getShowDownloadMessage(mStatus);
- }
-
- public boolean getShowResendMessage() {
- return MessageData.getShowResendMessage(mStatus);
- }
-
- public boolean getCanForwardMessage() {
- // Even for outgoing messages, we only allow forwarding if the message has finished sending
- // as media often has issues when send isn't complete
- return (mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE ||
- mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE);
- }
-
- public boolean getCanCopyMessageToClipboard() {
- return (hasText() &&
- (!getIsIncoming() || mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE));
- }
-
- public boolean getOneClickResendMessage() {
- return MessageData.getOneClickResendMessage(mStatus, mRawTelephonyStatus);
- }
-
- /**
- * Get sender's lookup uri.
- * This method doesn't support corp contacts.
- *
- * @return Lookup uri of sender's contact
- */
- public Uri getSenderContactLookupUri() {
- if (mSenderContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
- && !TextUtils.isEmpty(mSenderContactLookupKey)) {
- return ContactsContract.Contacts.getLookupUri(mSenderContactId,
- mSenderContactLookupKey);
- }
- return null;
- }
-
- public boolean getCanClusterWithPreviousMessage() {
- return mCanClusterWithPreviousMessage;
- }
-
- public boolean getCanClusterWithNextMessage() {
- return mCanClusterWithNextMessage;
- }
-
- @Override
- public String toString() {
- return MessageData.toString(mMessageId, mParts);
- }
-
- // Data definitions
-
- public static final String getConversationMessagesQuerySql() {
- return CONVERSATION_MESSAGES_QUERY_SQL
- + " AND "
- // Inject the conversation id
- + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)"
- + CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY;
- }
-
- static final String getConversationMessageIdsQuerySql() {
- return CONVERSATION_MESSAGES_IDS_QUERY_SQL
- + " AND "
- // Inject the conversation id
- + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)"
- + CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY;
- }
-
- public static final String getNotificationQuerySql() {
- return CONVERSATION_MESSAGES_QUERY_SQL
- + " AND "
- + "(" + DatabaseHelper.MessageColumns.STATUS + " in ("
- + MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", "
- + MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")"
- + " AND "
- + DatabaseHelper.MessageColumns.SEEN + " = 0)"
- + ")"
- + NOTIFICATION_QUERY_SQL_GROUP_BY;
- }
-
- public static final String getWearableQuerySql() {
- return CONVERSATION_MESSAGES_QUERY_SQL
- + " AND "
- + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?"
- + " AND "
- + DatabaseHelper.MessageColumns.STATUS + " IN ("
- + MessageData.BUGLE_STATUS_OUTGOING_DELIVERED + ", "
- + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE + ", "
- + MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ", "
- + MessageData.BUGLE_STATUS_OUTGOING_SENDING + ", "
- + MessageData.BUGLE_STATUS_OUTGOING_RESENDING + ", "
- + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ", "
- + MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", "
- + MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")"
- + ")"
- + NOTIFICATION_QUERY_SQL_GROUP_BY;
- }
-
- /*
- * Generate a sqlite snippet to call the quote function on the columnName argument.
- * The columnName doesn't strictly have to be a column name (e.g. it could be an
- * expression).
- */
- private static String quote(final String columnName) {
- return "quote(" + columnName + ")";
- }
-
- private static String makeGroupConcatString(final String column) {
- return "group_concat(" + column + ", '" + DIVIDER + "')";
- }
-
- private static String makeIfNullString(final String column) {
- return "ifnull(" + column + "," + "''" + ")";
- }
-
- private static String makePartsTableColumnString(final String column) {
- return DatabaseHelper.PARTS_TABLE + '.' + column;
- }
-
- private static String makeCaseWhenString(final String column,
- final boolean quote,
- final String asColumn) {
- final String fullColumn = makeIfNullString(makePartsTableColumnString(column));
- final String groupConcatTerm = quote
- ? makeGroupConcatString(quote(fullColumn))
- : makeGroupConcatString(fullColumn);
- return "CASE WHEN (" + CONVERSATION_MESSAGE_VIEW_PARTS_COUNT + ">1) THEN " + groupConcatTerm
- + " ELSE " + makePartsTableColumnString(column) + " END AS " + asColumn;
- }
-
- private static final String CONVERSATION_MESSAGE_VIEW_PARTS_COUNT =
- "count(" + DatabaseHelper.PARTS_TABLE + '.' + PartColumns._ID + ")";
-
- private static final String EMPTY_STRING = "";
-
- private static final String CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL =
- DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
- + " as " + ConversationMessageViewColumns._ID + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID
- + " as " + ConversationMessageViewColumns.CONVERSATION_ID + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
- + " as " + ConversationMessageViewColumns.PARTICIPANT_ID + ", "
-
- + makeCaseWhenString(PartColumns._ID, false,
- ConversationMessageViewColumns.PARTS_IDS) + ", "
- + makeCaseWhenString(PartColumns.CONTENT_TYPE, true,
- ConversationMessageViewColumns.PARTS_CONTENT_TYPES) + ", "
- + makeCaseWhenString(PartColumns.CONTENT_URI, true,
- ConversationMessageViewColumns.PARTS_CONTENT_URIS) + ", "
- + makeCaseWhenString(PartColumns.WIDTH, false,
- ConversationMessageViewColumns.PARTS_WIDTHS) + ", "
- + makeCaseWhenString(PartColumns.HEIGHT, false,
- ConversationMessageViewColumns.PARTS_HEIGHTS) + ", "
- + makeCaseWhenString(PartColumns.TEXT, true,
- ConversationMessageViewColumns.PARTS_TEXTS) + ", "
-
- + CONVERSATION_MESSAGE_VIEW_PARTS_COUNT
- + " as " + ConversationMessageViewColumns.PARTS_COUNT + ", "
-
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENT_TIMESTAMP
- + " as " + ConversationMessageViewColumns.SENT_TIMESTAMP + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP
- + " as " + ConversationMessageViewColumns.RECEIVED_TIMESTAMP + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SEEN
- + " as " + ConversationMessageViewColumns.SEEN + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ
- + " as " + ConversationMessageViewColumns.READ + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.PROTOCOL
- + " as " + ConversationMessageViewColumns.PROTOCOL + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
- + " as " + ConversationMessageViewColumns.STATUS + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_URI
- + " as " + ConversationMessageViewColumns.SMS_MESSAGE_URI + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_PRIORITY
- + " as " + ConversationMessageViewColumns.SMS_PRIORITY + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_SIZE
- + " as " + ConversationMessageViewColumns.SMS_MESSAGE_SIZE + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_SUBJECT
- + " as " + ConversationMessageViewColumns.MMS_SUBJECT + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_EXPIRY
- + " as " + ConversationMessageViewColumns.MMS_EXPIRY + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS
- + " as " + ConversationMessageViewColumns.RAW_TELEPHONY_STATUS + ", "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SELF_PARTICIPANT_ID
- + " as " + ConversationMessageViewColumns.SELF_PARTICIPANT_ID + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FULL_NAME
- + " as " + ConversationMessageViewColumns.SENDER_FULL_NAME + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME
- + " as " + ConversationMessageViewColumns.SENDER_FIRST_NAME + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
- + " as " + ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.NORMALIZED_DESTINATION
- + " as " + ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.PROFILE_PHOTO_URI
- + " as " + ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.CONTACT_ID
- + " as " + ConversationMessageViewColumns.SENDER_CONTACT_ID + ", "
- + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.LOOKUP_KEY
- + " as " + ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY + " ";
-
- private static final String CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL =
- " FROM " + DatabaseHelper.MESSAGES_TABLE
- + " LEFT JOIN " + DatabaseHelper.PARTS_TABLE
- + " ON (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns._ID
- + "=" + DatabaseHelper.PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ") "
- + " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE
- + " ON (" + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
- + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns._ID + ")"
- // Exclude draft messages from main view
- + " WHERE (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.STATUS
- + " <> " + MessageData.BUGLE_STATUS_OUTGOING_DRAFT;
-
- // This query is mostly static, except for the injection of conversation id. This is for
- // performance reasons, to ensure that the query uses indices and does not trigger full scans
- // of the messages table. See b/17160946 for more details.
- private static final String CONVERSATION_MESSAGES_QUERY_SQL = "SELECT "
- + CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL
- + CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL;
-
- private static final String CONVERSATION_MESSAGE_IDS_PROJECTION_SQL =
- DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
- + " as " + ConversationMessageViewColumns._ID + " ";
-
- private static final String CONVERSATION_MESSAGES_IDS_QUERY_SQL = "SELECT "
- + CONVERSATION_MESSAGE_IDS_PROJECTION_SQL
- + CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL;
-
- // Note that we sort DESC and ConversationData reverses the cursor. This is a performance
- // issue (improvement) for large cursors.
- private static final String CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY =
- " GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
- + " ORDER BY "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC";
-
- private static final String NOTIFICATION_QUERY_SQL_GROUP_BY =
- " GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
- + " ORDER BY "
- + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC";
-
- interface ConversationMessageViewColumns extends BaseColumns {
- static final String _ID = MessageColumns._ID;
- static final String CONVERSATION_ID = MessageColumns.CONVERSATION_ID;
- static final String PARTICIPANT_ID = MessageColumns.SENDER_PARTICIPANT_ID;
- static final String PARTS_COUNT = "parts_count";
- static final String SENT_TIMESTAMP = MessageColumns.SENT_TIMESTAMP;
- static final String RECEIVED_TIMESTAMP = MessageColumns.RECEIVED_TIMESTAMP;
- static final String SEEN = MessageColumns.SEEN;
- static final String READ = MessageColumns.READ;
- static final String PROTOCOL = MessageColumns.PROTOCOL;
- static final String STATUS = MessageColumns.STATUS;
- static final String SMS_MESSAGE_URI = MessageColumns.SMS_MESSAGE_URI;
- static final String SMS_PRIORITY = MessageColumns.SMS_PRIORITY;
- static final String SMS_MESSAGE_SIZE = MessageColumns.SMS_MESSAGE_SIZE;
- static final String MMS_SUBJECT = MessageColumns.MMS_SUBJECT;
- static final String MMS_EXPIRY = MessageColumns.MMS_EXPIRY;
- static final String RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS;
- static final String SELF_PARTICIPANT_ID = MessageColumns.SELF_PARTICIPANT_ID;
- static final String SENDER_FULL_NAME = ParticipantColumns.FULL_NAME;
- static final String SENDER_FIRST_NAME = ParticipantColumns.FIRST_NAME;
- static final String SENDER_DISPLAY_DESTINATION = ParticipantColumns.DISPLAY_DESTINATION;
- static final String SENDER_NORMALIZED_DESTINATION =
- ParticipantColumns.NORMALIZED_DESTINATION;
- static final String SENDER_PROFILE_PHOTO_URI = ParticipantColumns.PROFILE_PHOTO_URI;
- static final String SENDER_CONTACT_ID = ParticipantColumns.CONTACT_ID;
- static final String SENDER_CONTACT_LOOKUP_KEY = ParticipantColumns.LOOKUP_KEY;
- static final String PARTS_IDS = "parts_ids";
- static final String PARTS_CONTENT_TYPES = "parts_content_types";
- static final String PARTS_CONTENT_URIS = "parts_content_uris";
- static final String PARTS_WIDTHS = "parts_widths";
- static final String PARTS_HEIGHTS = "parts_heights";
- static final String PARTS_TEXTS = "parts_texts";
- }
-
- private static int sIndexIncrementer = 0;
-
- private static final int INDEX_MESSAGE_ID = sIndexIncrementer++;
- private static final int INDEX_CONVERSATION_ID = sIndexIncrementer++;
- private static final int INDEX_PARTICIPANT_ID = sIndexIncrementer++;
-
- private static final int INDEX_PARTS_IDS = sIndexIncrementer++;
- private static final int INDEX_PARTS_CONTENT_TYPES = sIndexIncrementer++;
- private static final int INDEX_PARTS_CONTENT_URIS = sIndexIncrementer++;
- private static final int INDEX_PARTS_WIDTHS = sIndexIncrementer++;
- private static final int INDEX_PARTS_HEIGHTS = sIndexIncrementer++;
- private static final int INDEX_PARTS_TEXTS = sIndexIncrementer++;
-
- private static final int INDEX_PARTS_COUNT = sIndexIncrementer++;
-
- private static final int INDEX_SENT_TIMESTAMP = sIndexIncrementer++;
- private static final int INDEX_RECEIVED_TIMESTAMP = sIndexIncrementer++;
- private static final int INDEX_SEEN = sIndexIncrementer++;
- private static final int INDEX_READ = sIndexIncrementer++;
- private static final int INDEX_PROTOCOL = sIndexIncrementer++;
- private static final int INDEX_STATUS = sIndexIncrementer++;
- private static final int INDEX_SMS_MESSAGE_URI = sIndexIncrementer++;
- private static final int INDEX_SMS_PRIORITY = sIndexIncrementer++;
- private static final int INDEX_SMS_MESSAGE_SIZE = sIndexIncrementer++;
- private static final int INDEX_MMS_SUBJECT = sIndexIncrementer++;
- private static final int INDEX_MMS_EXPIRY = sIndexIncrementer++;
- private static final int INDEX_RAW_TELEPHONY_STATUS = sIndexIncrementer++;
- private static final int INDEX_SELF_PARTICIPIANT_ID = sIndexIncrementer++;
- private static final int INDEX_SENDER_FULL_NAME = sIndexIncrementer++;
- private static final int INDEX_SENDER_FIRST_NAME = sIndexIncrementer++;
- private static final int INDEX_SENDER_DISPLAY_DESTINATION = sIndexIncrementer++;
- private static final int INDEX_SENDER_NORMALIZED_DESTINATION = sIndexIncrementer++;
- private static final int INDEX_SENDER_PROFILE_PHOTO_URI = sIndexIncrementer++;
- private static final int INDEX_SENDER_CONTACT_ID = sIndexIncrementer++;
- private static final int INDEX_SENDER_CONTACT_LOOKUP_KEY = sIndexIncrementer++;
-
-
- private static String[] sProjection = {
- ConversationMessageViewColumns._ID,
- ConversationMessageViewColumns.CONVERSATION_ID,
- ConversationMessageViewColumns.PARTICIPANT_ID,
-
- ConversationMessageViewColumns.PARTS_IDS,
- ConversationMessageViewColumns.PARTS_CONTENT_TYPES,
- ConversationMessageViewColumns.PARTS_CONTENT_URIS,
- ConversationMessageViewColumns.PARTS_WIDTHS,
- ConversationMessageViewColumns.PARTS_HEIGHTS,
- ConversationMessageViewColumns.PARTS_TEXTS,
-
- ConversationMessageViewColumns.PARTS_COUNT,
- ConversationMessageViewColumns.SENT_TIMESTAMP,
- ConversationMessageViewColumns.RECEIVED_TIMESTAMP,
- ConversationMessageViewColumns.SEEN,
- ConversationMessageViewColumns.READ,
- ConversationMessageViewColumns.PROTOCOL,
- ConversationMessageViewColumns.STATUS,
- ConversationMessageViewColumns.SMS_MESSAGE_URI,
- ConversationMessageViewColumns.SMS_PRIORITY,
- ConversationMessageViewColumns.SMS_MESSAGE_SIZE,
- ConversationMessageViewColumns.MMS_SUBJECT,
- ConversationMessageViewColumns.MMS_EXPIRY,
- ConversationMessageViewColumns.RAW_TELEPHONY_STATUS,
- ConversationMessageViewColumns.SELF_PARTICIPANT_ID,
- ConversationMessageViewColumns.SENDER_FULL_NAME,
- ConversationMessageViewColumns.SENDER_FIRST_NAME,
- ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION,
- ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION,
- ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
- ConversationMessageViewColumns.SENDER_CONTACT_ID,
- ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY,
- };
-
- public static String[] getProjection() {
- return sProjection;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java b/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java
deleted file mode 100644
index 0b5ef51..0000000
--- a/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.database.Cursor;
-import android.support.v4.util.SimpleArrayMap;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-/**
- * A class that contains the list of all participants potentially involved in a conversation.
- * Includes both the participant records for each participant referenced in conversation
- * participants table (i.e. "other" phone numbers) plus all participants representing self
- * (i.e. one per sim recorded in the subscription manager db).
- */
-public class ConversationParticipantsData implements Iterable<ParticipantData> {
- // A map from a participant id to a participant
- private final SimpleArrayMap<String, ParticipantData> mConversationParticipantsMap;
- private int mParticipantCountExcludingSelf = 0;
-
- public ConversationParticipantsData() {
- mConversationParticipantsMap = new SimpleArrayMap<String, ParticipantData>();
- }
-
- public void bind(final Cursor cursor) {
- mConversationParticipantsMap.clear();
- mParticipantCountExcludingSelf = 0;
- if (cursor != null) {
- while (cursor.moveToNext()) {
- final ParticipantData newParticipant = ParticipantData.getFromCursor(cursor);
- if (!newParticipant.isSelf()) {
- mParticipantCountExcludingSelf++;
- }
- mConversationParticipantsMap.put(newParticipant.getId(), newParticipant);
- }
- }
- }
-
- @VisibleForTesting
- ParticipantData getParticipantById(final String participantId) {
- return mConversationParticipantsMap.get(participantId);
- }
-
- ArrayList<ParticipantData> getParticipantListExcludingSelf() {
- final ArrayList<ParticipantData> retList =
- new ArrayList<ParticipantData>(mConversationParticipantsMap.size());
- for (int i = 0; i < mConversationParticipantsMap.size(); i++) {
- final ParticipantData participant = mConversationParticipantsMap.valueAt(i);
- if (!participant.isSelf()) {
- retList.add(participant);
- }
- }
- return retList;
- }
-
- /**
- * For a 1:1 conversation return the other (not self) participant
- */
- public ParticipantData getOtherParticipant() {
- if (mParticipantCountExcludingSelf == 1) {
- for (int i = 0; i < mConversationParticipantsMap.size(); i++) {
- final ParticipantData participant = mConversationParticipantsMap.valueAt(i);
- if (!participant.isSelf()) {
- return participant;
- }
- }
- Assert.fail();
- }
- return null;
- }
-
- public int getNumberOfParticipantsExcludingSelf() {
- return mParticipantCountExcludingSelf;
- }
-
- public boolean isLoaded() {
- return !mConversationParticipantsMap.isEmpty();
- }
-
- @Override
- public Iterator<ParticipantData> iterator() {
- return new Iterator<ParticipantData>() {
- private int mCurrentIndex = -1;
-
- @Override
- public boolean hasNext() {
- return mCurrentIndex < mConversationParticipantsMap.size() - 1;
- }
-
- @Override
- public ParticipantData next() {
- mCurrentIndex++;
- if (mCurrentIndex >= mConversationParticipantsMap.size()) {
- throw new NoSuchElementException();
- }
- return mConversationParticipantsMap.valueAt(mCurrentIndex);
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/DraftMessageData.java b/src/com/android/messaging/datamodel/data/DraftMessageData.java
deleted file mode 100644
index 7a7199a..0000000
--- a/src/com/android/messaging/datamodel/data/DraftMessageData.java
+++ /dev/null
@@ -1,855 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.MessageTextStats;
-import com.android.messaging.datamodel.action.ReadDraftDataAction;
-import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener;
-import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionMonitor;
-import com.android.messaging.datamodel.action.WriteDraftMessageAction;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-public class DraftMessageData extends BindableData implements ReadDraftDataActionListener {
-
- /**
- * Interface for DraftMessageData listeners
- */
- public interface DraftMessageDataListener {
- @RunsOnMainThread
- void onDraftChanged(DraftMessageData data, int changeFlags);
-
- @RunsOnMainThread
- void onDraftAttachmentLimitReached(DraftMessageData data);
-
- @RunsOnMainThread
- void onDraftAttachmentLoadFailed();
- }
-
- /**
- * Interface for providing subscription-related data to DraftMessageData
- */
- public interface DraftMessageSubscriptionDataProvider {
- int getConversationSelfSubId();
- }
-
- // Flags sent to onDraftChanged to help the receiver limit the amount of work done
- public static int ATTACHMENTS_CHANGED = 0x0001;
- public static int MESSAGE_TEXT_CHANGED = 0x0002;
- public static int MESSAGE_SUBJECT_CHANGED = 0x0004;
- // Whether the self participant data has been loaded
- public static int SELF_CHANGED = 0x0008;
- public static int ALL_CHANGED = 0x00FF;
- // ALL_CHANGED intentionally doesn't include WIDGET_CHANGED. ConversationFragment needs to
- // be notified if the draft it is looking at is changed externally (by a desktop widget) so it
- // can reload the draft.
- public static int WIDGET_CHANGED = 0x0100;
-
- private final String mConversationId;
- private ReadDraftDataActionMonitor mMonitor;
- private final DraftMessageDataEventDispatcher mListeners;
- private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
-
- private boolean mIncludeEmailAddress;
- private boolean mIsGroupConversation;
- private String mMessageText;
- private String mMessageSubject;
- private String mSelfId;
- private MessageTextStats mMessageTextStats;
- private boolean mSending;
-
- /** Keeps track of completed attachments in the message draft. This data is persisted to db */
- private final List<MessagePartData> mAttachments;
-
- /** A read-only wrapper on mAttachments surfaced to the UI layer for rendering */
- private final List<MessagePartData> mReadOnlyAttachments;
-
- /** Keeps track of pending attachments that are being loaded. The pending attachments are
- * transient, because they are not persisted to the database and are dropped once we go
- * to the background (after the UI calls saveToStorage) */
- private final List<PendingAttachmentData> mPendingAttachments;
-
- /** A read-only wrapper on mPendingAttachments surfaced to the UI layer for rendering */
- private final List<PendingAttachmentData> mReadOnlyPendingAttachments;
-
- /** Is the current draft a cached copy of what's been saved to the database. If so, we
- * may skip loading from database if we are still bound */
- private boolean mIsDraftCachedCopy;
-
- /** Whether we are currently asynchronously validating the draft before sending. */
- private CheckDraftForSendTask mCheckDraftForSendTask;
-
- public DraftMessageData(final String conversationId) {
- mConversationId = conversationId;
- mAttachments = new ArrayList<MessagePartData>();
- mReadOnlyAttachments = Collections.unmodifiableList(mAttachments);
- mPendingAttachments = new ArrayList<PendingAttachmentData>();
- mReadOnlyPendingAttachments = Collections.unmodifiableList(mPendingAttachments);
- mListeners = new DraftMessageDataEventDispatcher();
- mMessageTextStats = new MessageTextStats();
- }
-
- public void addListener(final DraftMessageDataListener listener) {
- mListeners.add(listener);
- }
-
- public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
- mSubscriptionDataProvider = provider;
- }
-
- public void updateFromMessageData(final MessageData message, final String bindingId) {
- // New attachments have arrived - only update if the user hasn't already edited
- Assert.notNull(bindingId);
- // The draft is now synced with actual MessageData and no longer a cached copy.
- mIsDraftCachedCopy = false;
- // Do not use the loaded draft if the user began composing a message before the draft loaded
- // During config changes (orientation), the text fields preserve their data, so allow them
- // to be the same and still consider the draft unchanged by the user
- if (isDraftEmpty() || (TextUtils.equals(mMessageText, message.getMessageText()) &&
- TextUtils.equals(mMessageSubject, message.getMmsSubject()) &&
- mAttachments.isEmpty())) {
- // No need to clear as just checked it was empty or a subset
- setMessageText(message.getMessageText(), false /* notify */);
- setMessageSubject(message.getMmsSubject(), false /* notify */);
- for (final MessagePartData part : message.getParts()) {
- if (part.isAttachment() && getAttachmentCount() >= getAttachmentLimit()) {
- dispatchAttachmentLimitReached();
- break;
- }
-
- if (part instanceof PendingAttachmentData) {
- // This is a pending attachment data from share intent (e.g. an shared image
- // that we need to persist locally).
- final PendingAttachmentData data = (PendingAttachmentData) part;
- Assert.equals(PendingAttachmentData.STATE_PENDING, data.getCurrentState());
- addOnePendingAttachmentNoNotify(data, bindingId);
- } else if (part.isAttachment()) {
- addOneAttachmentNoNotify(part);
- }
- }
- dispatchChanged(ALL_CHANGED);
- } else {
- // The user has started a new message so we throw out the draft message data if there
- // is one but we also loaded the self metadata and need to let our listeners know.
- dispatchChanged(SELF_CHANGED);
- }
- }
-
- /**
- * Create a MessageData object containing a copy of all the parts in this DraftMessageData.
- *
- * @param clearLocalCopy whether we should clear out the in-memory copy of the parts. If we
- * are simply pausing/resuming and not sending the message, then we can keep
- * @return the MessageData for the draft, null if self id is not set
- */
- public MessageData createMessageWithCurrentAttachments(final boolean clearLocalCopy) {
- MessageData message = null;
- if (getIsMms()) {
- message = MessageData.createDraftMmsMessage(mConversationId, mSelfId,
- mMessageText, mMessageSubject);
- for (final MessagePartData attachment : mAttachments) {
- message.addPart(attachment);
- }
- } else {
- message = MessageData.createDraftSmsMessage(mConversationId, mSelfId,
- mMessageText);
- }
-
- if (clearLocalCopy) {
- // The message now owns all the attachments and the text...
- clearLocalDraftCopy();
- dispatchChanged(ALL_CHANGED);
- } else {
- // The draft message becomes a cached copy for UI.
- mIsDraftCachedCopy = true;
- }
- return message;
- }
-
- private void clearLocalDraftCopy() {
- mIsDraftCachedCopy = false;
- mAttachments.clear();
- setMessageText("");
- setMessageSubject("");
- }
-
- public String getConversationId() {
- return mConversationId;
- }
-
- public String getMessageText() {
- return mMessageText;
- }
-
- public String getMessageSubject() {
- return mMessageSubject;
- }
-
- public boolean getIsMms() {
- final int selfSubId = getSelfSubId();
- return MmsSmsUtils.getRequireMmsForEmailAddress(mIncludeEmailAddress, selfSubId) ||
- (mIsGroupConversation && MmsUtils.groupMmsEnabled(selfSubId)) ||
- mMessageTextStats.getMessageLengthRequiresMms() || !mAttachments.isEmpty() ||
- !TextUtils.isEmpty(mMessageSubject);
- }
-
- public boolean getIsGroupMmsConversation() {
- return getIsMms() && mIsGroupConversation;
- }
-
- public String getSelfId() {
- return mSelfId;
- }
-
- public int getNumMessagesToBeSent() {
- return mMessageTextStats.getNumMessagesToBeSent();
- }
-
- public int getCodePointsRemainingInCurrentMessage() {
- return mMessageTextStats.getCodePointsRemainingInCurrentMessage();
- }
-
- public int getSelfSubId() {
- return mSubscriptionDataProvider == null ? ParticipantData.DEFAULT_SELF_SUB_ID :
- mSubscriptionDataProvider.getConversationSelfSubId();
- }
-
- private void setMessageText(final String messageText, final boolean notify) {
- mMessageText = messageText;
- mMessageTextStats.updateMessageTextStats(getSelfSubId(), mMessageText);
- if (notify) {
- dispatchChanged(MESSAGE_TEXT_CHANGED);
- }
- }
-
- private void setMessageSubject(final String subject, final boolean notify) {
- mMessageSubject = subject;
- if (notify) {
- dispatchChanged(MESSAGE_SUBJECT_CHANGED);
- }
- }
-
- public void setMessageText(final String messageText) {
- setMessageText(messageText, false);
- }
-
- public void setMessageSubject(final String subject) {
- setMessageSubject(subject, false);
- }
-
- public void addAttachments(final Collection<? extends MessagePartData> attachments) {
- // If the incoming attachments contains a single-only attachment, we need to clear
- // the existing attachments.
- for (final MessagePartData data : attachments) {
- if (data.isSinglePartOnly()) {
- // clear any existing attachments because the attachment we're adding can only
- // exist by itself.
- destroyAttachments();
- break;
- }
- }
- // If the existing attachments contain a single-only attachment, we need to clear the
- // existing attachments to make room for the incoming attachment.
- for (final MessagePartData data : mAttachments) {
- if (data.isSinglePartOnly()) {
- // clear any existing attachments because the single attachment can only exist
- // by itself
- destroyAttachments();
- break;
- }
- }
- // If any of the pending attachments contain a single-only attachment, we need to clear the
- // existing attachments to make room for the incoming attachment.
- for (final MessagePartData data : mPendingAttachments) {
- if (data.isSinglePartOnly()) {
- // clear any existing attachments because the single attachment can only exist
- // by itself
- destroyAttachments();
- break;
- }
- }
-
- boolean reachedLimit = false;
- for (final MessagePartData data : attachments) {
- // Don't break out of loop even if limit has been reached so we can destroy all
- // of the over-limit attachments.
- reachedLimit |= addOneAttachmentNoNotify(data);
- }
- if (reachedLimit) {
- dispatchAttachmentLimitReached();
- }
- dispatchChanged(ATTACHMENTS_CHANGED);
- }
-
- public boolean containsAttachment(final Uri contentUri) {
- for (final MessagePartData existingAttachment : mAttachments) {
- if (existingAttachment.getContentUri().equals(contentUri)) {
- return true;
- }
- }
-
- for (final PendingAttachmentData pendingAttachment : mPendingAttachments) {
- if (pendingAttachment.getContentUri().equals(contentUri)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Try to add one attachment to the attachment list, while guarding against duplicates and
- * going over the limit.
- * @return true if the attachment limit was reached, false otherwise
- */
- private boolean addOneAttachmentNoNotify(final MessagePartData attachment) {
- Assert.isTrue(attachment.isAttachment());
- final boolean reachedLimit = getAttachmentCount() >= getAttachmentLimit();
- if (reachedLimit || containsAttachment(attachment.getContentUri())) {
- // Never go over the limit. Never add duplicated attachments.
- attachment.destroyAsync();
- return reachedLimit;
- } else {
- addAttachment(attachment, null /*pendingAttachment*/);
- return false;
- }
- }
-
- private void addAttachment(final MessagePartData attachment,
- final PendingAttachmentData pendingAttachment) {
- if (attachment != null && attachment.isSinglePartOnly()) {
- // clear any existing attachments because the attachment we're adding can only
- // exist by itself.
- destroyAttachments();
- }
- if (pendingAttachment != null && pendingAttachment.isSinglePartOnly()) {
- // clear any existing attachments because the attachment we're adding can only
- // exist by itself.
- destroyAttachments();
- }
- // If the existing attachments contain a single-only attachment, we need to clear the
- // existing attachments to make room for the incoming attachment.
- for (final MessagePartData data : mAttachments) {
- if (data.isSinglePartOnly()) {
- // clear any existing attachments because the single attachment can only exist
- // by itself
- destroyAttachments();
- break;
- }
- }
- // If any of the pending attachments contain a single-only attachment, we need to clear the
- // existing attachments to make room for the incoming attachment.
- for (final MessagePartData data : mPendingAttachments) {
- if (data.isSinglePartOnly()) {
- // clear any existing attachments because the single attachment can only exist
- // by itself
- destroyAttachments();
- break;
- }
- }
- if (attachment != null) {
- mAttachments.add(attachment);
- } else if (pendingAttachment != null) {
- mPendingAttachments.add(pendingAttachment);
- }
- }
-
- public void addPendingAttachment(final PendingAttachmentData pendingAttachment,
- final BindingBase<DraftMessageData> binding) {
- final boolean reachedLimit = addOnePendingAttachmentNoNotify(pendingAttachment,
- binding.getBindingId());
- if (reachedLimit) {
- dispatchAttachmentLimitReached();
- }
- dispatchChanged(ATTACHMENTS_CHANGED);
- }
-
- /**
- * Try to add one pending attachment, while guarding against duplicates and
- * going over the limit.
- * @return true if the attachment limit was reached, false otherwise
- */
- private boolean addOnePendingAttachmentNoNotify(final PendingAttachmentData pendingAttachment,
- final String bindingId) {
- final boolean reachedLimit = getAttachmentCount() >= getAttachmentLimit();
- if (reachedLimit || containsAttachment(pendingAttachment.getContentUri())) {
- // Never go over the limit. Never add duplicated attachments.
- pendingAttachment.destroyAsync();
- return reachedLimit;
- } else {
- Assert.isTrue(!mPendingAttachments.contains(pendingAttachment));
- Assert.equals(PendingAttachmentData.STATE_PENDING, pendingAttachment.getCurrentState());
- addAttachment(null /*attachment*/, pendingAttachment);
-
- pendingAttachment.loadAttachmentForDraft(this, bindingId);
- return false;
- }
- }
-
- public void setSelfId(final String selfId, final boolean notify) {
- LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: set selfId=" + selfId
- + " for conversationId=" + mConversationId);
- mSelfId = selfId;
- if (notify) {
- dispatchChanged(SELF_CHANGED);
- }
- }
-
- public boolean hasAttachments() {
- return !mAttachments.isEmpty();
- }
-
- public boolean hasPendingAttachments() {
- return !mPendingAttachments.isEmpty();
- }
-
- private int getAttachmentCount() {
- return mAttachments.size() + mPendingAttachments.size();
- }
-
- private int getVideoAttachmentCount() {
- int count = 0;
- for (MessagePartData part : mAttachments) {
- if (part.isVideo()) {
- count++;
- }
- }
- for (MessagePartData part : mPendingAttachments) {
- if (part.isVideo()) {
- count++;
- }
- }
- return count;
- }
-
- private int getAttachmentLimit() {
- return BugleGservices.get().getInt(
- BugleGservicesKeys.MMS_ATTACHMENT_LIMIT,
- BugleGservicesKeys.MMS_ATTACHMENT_LIMIT_DEFAULT);
- }
-
- public void removeAttachment(final MessagePartData attachment) {
- for (final MessagePartData existingAttachment : mAttachments) {
- if (existingAttachment.getContentUri().equals(attachment.getContentUri())) {
- mAttachments.remove(existingAttachment);
- existingAttachment.destroyAsync();
- dispatchChanged(ATTACHMENTS_CHANGED);
- break;
- }
- }
- }
-
- public void removeExistingAttachments(final Set<MessagePartData> attachmentsToRemove) {
- boolean removed = false;
- final Iterator<MessagePartData> iterator = mAttachments.iterator();
- while (iterator.hasNext()) {
- final MessagePartData existingAttachment = iterator.next();
- if (attachmentsToRemove.contains(existingAttachment)) {
- iterator.remove();
- existingAttachment.destroyAsync();
- removed = true;
- }
- }
-
- if (removed) {
- dispatchChanged(ATTACHMENTS_CHANGED);
- }
- }
-
- public void removePendingAttachment(final PendingAttachmentData pendingAttachment) {
- for (final PendingAttachmentData existingAttachment : mPendingAttachments) {
- if (existingAttachment.getContentUri().equals(pendingAttachment.getContentUri())) {
- mPendingAttachments.remove(pendingAttachment);
- pendingAttachment.destroyAsync();
- dispatchChanged(ATTACHMENTS_CHANGED);
- break;
- }
- }
- }
-
- public void updatePendingAttachment(final MessagePartData updatedAttachment,
- final PendingAttachmentData pendingAttachment) {
- for (final PendingAttachmentData existingAttachment : mPendingAttachments) {
- if (existingAttachment.getContentUri().equals(pendingAttachment.getContentUri())) {
- mPendingAttachments.remove(pendingAttachment);
- if (pendingAttachment.isSinglePartOnly()) {
- updatedAttachment.setSinglePartOnly(true);
- }
- mAttachments.add(updatedAttachment);
- dispatchChanged(ATTACHMENTS_CHANGED);
- return;
- }
- }
-
- // If we are here, this means the pending attachment has been dropped before the task
- // to load it was completed. In this case destroy the temporarily staged file since it
- // is no longer needed.
- updatedAttachment.destroyAsync();
- }
-
- /**
- * Remove the attachments from the draft and notify any listeners.
- * @param flags typically this will be ATTACHMENTS_CHANGED. When attachments are cleared in a
- * widget, flags will also contain WIDGET_CHANGED.
- */
- public void clearAttachments(final int flags) {
- destroyAttachments();
- dispatchChanged(flags);
- }
-
- public List<MessagePartData> getReadOnlyAttachments() {
- return mReadOnlyAttachments;
- }
-
- public List<PendingAttachmentData> getReadOnlyPendingAttachments() {
- return mReadOnlyPendingAttachments;
- }
-
- public boolean loadFromStorage(final BindingBase<DraftMessageData> binding,
- final MessageData optionalIncomingDraft, boolean clearLocalDraft) {
- LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: "
- + (optionalIncomingDraft == null ? "loading" : "setting")
- + " for conversationId=" + mConversationId);
- if (clearLocalDraft) {
- clearLocalDraftCopy();
- }
- final boolean isDraftCachedCopy = mIsDraftCachedCopy;
- mIsDraftCachedCopy = false;
- // Before reading message from db ensure the caller is bound to us (and knows the id)
- if (mMonitor == null && !isDraftCachedCopy && isBound(binding.getBindingId())) {
- mMonitor = ReadDraftDataAction.readDraftData(mConversationId,
- optionalIncomingDraft, binding.getBindingId(), this);
- return true;
- }
- return false;
- }
-
- /**
- * Saves the current draft to db. This will save the draft and drop any pending attachments
- * we have. The UI typically goes into the background when this is called, and instead of
- * trying to persist the state of the pending attachments (the app may be killed, the activity
- * may be destroyed), we simply drop the pending attachments for consistency.
- */
- public void saveToStorage(final BindingBase<DraftMessageData> binding) {
- saveToStorageInternal(binding);
- dropPendingAttachments();
- }
-
- private void saveToStorageInternal(final BindingBase<DraftMessageData> binding) {
- // Create MessageData to store to db, but don't clear the in-memory copy so UI will
- // continue to display it.
- // If self id is null then we'll not attempt to change the conversation's self id.
- final MessageData message = createMessageWithCurrentAttachments(false /* clearLocalCopy */);
- // Before writing message to db ensure the caller is bound to us (and knows the id)
- if (isBound(binding.getBindingId())){
- WriteDraftMessageAction.writeDraftMessage(mConversationId, message);
- }
- }
-
- /**
- * Called when we are ready to send the message. This will assemble/return the MessageData for
- * sending and clear the local draft data, both from memory and from DB. This will also bind
- * the message data with a self Id through which the message will be sent.
- *
- * @param binding the binding object from our consumer. We need to make sure we are still bound
- * to that binding before saving to storage.
- */
- public MessageData prepareMessageForSending(final BindingBase<DraftMessageData> binding) {
- // We can't send the message while there's still stuff pending.
- Assert.isTrue(!hasPendingAttachments());
- mSending = true;
- // Assembles the message to send and empty working draft data.
- // If self id is null then message is sent with conversation's self id.
- final MessageData messageToSend =
- createMessageWithCurrentAttachments(true /* clearLocalCopy */);
- // Note sending message will empty the draft data in DB.
- mSending = false;
- return messageToSend;
- }
-
- public boolean isSending() {
- return mSending;
- }
-
- @Override // ReadDraftMessageActionListener.onReadDraftMessageSucceeded
- public void onReadDraftDataSucceeded(final ReadDraftDataAction action, final Object data,
- final MessageData message, final ConversationListItemData conversation) {
- final String bindingId = (String) data;
-
- // Before passing draft message on to ui ensure the data is bound to the same bindingid
- if (isBound(bindingId)) {
- mSelfId = message.getSelfId();
- mIsGroupConversation = conversation.getIsGroup();
- mIncludeEmailAddress = conversation.getIncludeEmailAddress();
- updateFromMessageData(message, bindingId);
- LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: draft loaded. "
- + "conversationId=" + mConversationId + " selfId=" + mSelfId);
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "DraftMessageData: draft loaded but not bound. "
- + "conversationId=" + mConversationId);
- }
- mMonitor = null;
- }
-
- @Override // ReadDraftMessageActionListener.onReadDraftDataFailed
- public void onReadDraftDataFailed(final ReadDraftDataAction action, final Object data) {
- LogUtil.w(LogUtil.BUGLE_TAG, "DraftMessageData: draft not loaded. "
- + "conversationId=" + mConversationId);
- // The draft is now synced with actual MessageData and no longer a cached copy.
- mIsDraftCachedCopy = false;
- // Just clear the monitor - no update to draft data
- mMonitor = null;
- }
-
- /**
- * Check if Bugle is default sms app
- * @return
- */
- public boolean getIsDefaultSmsApp() {
- return PhoneUtils.getDefault().isDefaultSmsApp();
- }
-
- @Override //BindableData.unregisterListeners
- protected void unregisterListeners() {
- if (mMonitor != null) {
- mMonitor.unregister();
- }
- mMonitor = null;
- mListeners.clear();
- }
-
- private void destroyAttachments() {
- for (final MessagePartData attachment : mAttachments) {
- attachment.destroyAsync();
- }
- mAttachments.clear();
- mPendingAttachments.clear();
- }
-
- private void dispatchChanged(final int changeFlags) {
- // No change is expected to be made to the draft if it is in cached copy state.
- if (mIsDraftCachedCopy) {
- return;
- }
- // Any change in the draft will cancel any pending draft checking task, since the
- // size/status of the draft may have changed.
- if (mCheckDraftForSendTask != null) {
- mCheckDraftForSendTask.cancel(true /* mayInterruptIfRunning */);
- mCheckDraftForSendTask = null;
- }
- mListeners.onDraftChanged(this, changeFlags);
- }
-
- private void dispatchAttachmentLimitReached() {
- mListeners.onDraftAttachmentLimitReached(this);
- }
-
- /**
- * Drop any pending attachments that haven't finished. This is called after the UI goes to
- * the background and we persist the draft data to the database.
- */
- private void dropPendingAttachments() {
- mPendingAttachments.clear();
- }
-
- private boolean isDraftEmpty() {
- return TextUtils.isEmpty(mMessageText) && mAttachments.isEmpty() &&
- TextUtils.isEmpty(mMessageSubject);
- }
-
- public boolean isCheckingDraft() {
- return mCheckDraftForSendTask != null && !mCheckDraftForSendTask.isCancelled();
- }
-
- public void checkDraftForAction(final boolean checkMessageSize, final int selfSubId,
- final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
- new CheckDraftForSendTask(checkMessageSize, selfSubId, callback, binding)
- .executeOnThreadPool((Void) null);
- }
-
- /**
- * Allows us to have multiple data listeners for DraftMessageData
- */
- private class DraftMessageDataEventDispatcher
- extends ArrayList<DraftMessageDataListener>
- implements DraftMessageDataListener {
-
- @Override
- @RunsOnMainThread
- public void onDraftChanged(DraftMessageData data, int changeFlags) {
- Assert.isMainThread();
- for (final DraftMessageDataListener listener : this) {
- listener.onDraftChanged(data, changeFlags);
- }
- }
-
- @Override
- @RunsOnMainThread
- public void onDraftAttachmentLimitReached(DraftMessageData data) {
- Assert.isMainThread();
- for (final DraftMessageDataListener listener : this) {
- listener.onDraftAttachmentLimitReached(data);
- }
- }
-
- @Override
- @RunsOnMainThread
- public void onDraftAttachmentLoadFailed() {
- Assert.isMainThread();
- for (final DraftMessageDataListener listener : this) {
- listener.onDraftAttachmentLoadFailed();
- }
- }
- }
-
- public interface CheckDraftTaskCallback {
- void onDraftChecked(DraftMessageData data, int result);
- }
-
- public class CheckDraftForSendTask extends SafeAsyncTask<Void, Void, Integer> {
- public static final int RESULT_PASSED = 0;
- public static final int RESULT_HAS_PENDING_ATTACHMENTS = 1;
- public static final int RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS = 2;
- public static final int RESULT_MESSAGE_OVER_LIMIT = 3;
- public static final int RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED = 4;
- public static final int RESULT_SIM_NOT_READY = 5;
- private final boolean mCheckMessageSize;
- private final int mSelfSubId;
- private final CheckDraftTaskCallback mCallback;
- private final String mBindingId;
- private final List<MessagePartData> mAttachmentsCopy;
- private int mPreExecuteResult = RESULT_PASSED;
-
- public CheckDraftForSendTask(final boolean checkMessageSize, final int selfSubId,
- final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
- mCheckMessageSize = checkMessageSize;
- mSelfSubId = selfSubId;
- mCallback = callback;
- mBindingId = binding.getBindingId();
- // Obtain an immutable copy of the attachment list so we can operate on it in the
- // background thread.
- mAttachmentsCopy = new ArrayList<MessagePartData>(mAttachments);
-
- mCheckDraftForSendTask = this;
- }
-
- @Override
- protected void onPreExecute() {
- // Perform checking work that can happen on the main thread.
- if (hasPendingAttachments()) {
- mPreExecuteResult = RESULT_HAS_PENDING_ATTACHMENTS;
- return;
- }
- if (getIsGroupMmsConversation()) {
- try {
- if (TextUtils.isEmpty(PhoneUtils.get(mSelfSubId).getSelfRawNumber(true))) {
- mPreExecuteResult = RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS;
- return;
- }
- } catch (IllegalStateException e) {
- // This happens when there is no active subscription, e.g. on Nova
- // when the phone switches carrier.
- mPreExecuteResult = RESULT_SIM_NOT_READY;
- return;
- }
- }
- if (getVideoAttachmentCount() > MmsUtils.MAX_VIDEO_ATTACHMENT_COUNT) {
- mPreExecuteResult = RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED;
- return;
- }
- }
-
- @Override
- protected Integer doInBackgroundTimed(Void... params) {
- if (mPreExecuteResult != RESULT_PASSED) {
- return mPreExecuteResult;
- }
-
- if (mCheckMessageSize && getIsMessageOverLimit()) {
- return RESULT_MESSAGE_OVER_LIMIT;
- }
- return RESULT_PASSED;
- }
-
- @Override
- protected void onPostExecute(Integer result) {
- mCheckDraftForSendTask = null;
- // Only call back if we are bound to the original binding.
- if (isBound(mBindingId) && !isCancelled()) {
- mCallback.onDraftChecked(DraftMessageData.this, result);
- } else {
- if (!isBound(mBindingId)) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft not bound");
- }
- if (isCancelled()) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft is cancelled");
- }
- }
- }
-
- @Override
- protected void onCancelled() {
- mCheckDraftForSendTask = null;
- }
-
- /**
- * 1. Check if the draft message contains too many attachments to send
- * 2. Computes the minimum size that this message could be compressed/downsampled/encoded
- * before sending and check if it meets the carrier max size for sending.
- * @see MessagePartData#getMinimumSizeInBytesForSending()
- */
- @DoesNotRunOnMainThread
- private boolean getIsMessageOverLimit() {
- Assert.isNotMainThread();
- if (mAttachmentsCopy.size() > getAttachmentLimit()) {
- return true;
- }
-
- // Aggregate the size from all the attachments.
- long totalSize = 0;
- for (final MessagePartData attachment : mAttachmentsCopy) {
- totalSize += attachment.getMinimumSizeInBytesForSending();
- }
- return totalSize > MmsConfig.get(mSelfSubId).getMaxMessageSize();
- }
- }
-
- public void onPendingAttachmentLoadFailed(PendingAttachmentData data) {
- mListeners.onDraftAttachmentLoadFailed();
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
deleted file mode 100644
index 6649757..0000000
--- a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.provider.BaseColumns;
-import android.provider.MediaStore.Images.Media;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.media.FileImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.util.Assert;
-
-/**
- * Provides data for GalleryGridItemView
- */
-public class GalleryGridItemData {
- public static final String[] IMAGE_PROJECTION = new String[] {
- Media._ID,
- Media.DATA,
- Media.WIDTH,
- Media.HEIGHT,
- Media.MIME_TYPE,
- Media.DATE_MODIFIED};
-
- public static final String[] SPECIAL_ITEM_COLUMNS = new String[] {
- BaseColumns._ID
- };
-
- private static final int INDEX_ID = 0;
-
- // For local image gallery.
- private static final int INDEX_DATA_PATH = 1;
- private static final int INDEX_WIDTH = 2;
- private static final int INDEX_HEIGHT = 3;
- private static final int INDEX_MIME_TYPE = 4;
- private static final int INDEX_DATE_MODIFIED = 5;
-
- /** A special item's id for picking images from document picker */
- public static final String ID_DOCUMENT_PICKER_ITEM = "-1";
-
- private UriImageRequestDescriptor mImageData;
- private String mContentType;
- private boolean mIsDocumentPickerItem;
- private long mDateSeconds;
-
- public GalleryGridItemData() {
- }
-
- public void bind(final Cursor cursor, final int desiredWidth, final int desiredHeight) {
- mIsDocumentPickerItem = TextUtils.equals(cursor.getString(INDEX_ID),
- ID_DOCUMENT_PICKER_ITEM);
- if (mIsDocumentPickerItem) {
- mImageData = null;
- mContentType = null;
- } else {
- int sourceWidth = cursor.getInt(INDEX_WIDTH);
- int sourceHeight = cursor.getInt(INDEX_HEIGHT);
-
- // Guard against bad data
- if (sourceWidth <= 0) {
- sourceWidth = ImageRequest.UNSPECIFIED_SIZE;
- }
- if (sourceHeight <= 0) {
- sourceHeight = ImageRequest.UNSPECIFIED_SIZE;
- }
-
- mContentType = cursor.getString(INDEX_MIME_TYPE);
- final String dateModified = cursor.getString(INDEX_DATE_MODIFIED);
- mDateSeconds = !TextUtils.isEmpty(dateModified) ? Long.parseLong(dateModified) : -1;
- mImageData = new FileImageRequestDescriptor(
- cursor.getString(INDEX_DATA_PATH),
- desiredWidth,
- desiredHeight,
- sourceWidth,
- sourceHeight,
- true /* canUseThumbnail */,
- true /* allowCompression */,
- true /* isStatic */);
- }
- }
-
- public boolean isDocumentPickerItem() {
- return mIsDocumentPickerItem;
- }
-
- public Uri getImageUri() {
- return mImageData.uri;
- }
-
- public UriImageRequestDescriptor getImageRequestDescriptor() {
- return mImageData;
- }
-
- public MessagePartData constructMessagePartData(final Rect startRect) {
- Assert.isTrue(!mIsDocumentPickerItem);
- return new MediaPickerMessagePartData(startRect, mContentType,
- mImageData.uri, mImageData.sourceWidth, mImageData.sourceHeight);
- }
-
- /**
- * @return The date in seconds. This can be negative if we could not retreive date info
- */
- public long getDateSeconds() {
- return mDateSeconds;
- }
-
- public String getContentType() {
- return mContentType;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/LaunchConversationData.java b/src/com/android/messaging/datamodel/data/LaunchConversationData.java
deleted file mode 100644
index 7eea580..0000000
--- a/src/com/android/messaging/datamodel/data/LaunchConversationData.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import com.android.messaging.datamodel.action.ActionMonitor;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.LogUtil;
-
-public class LaunchConversationData extends BindableData implements
- GetOrCreateConversationActionListener {
- public interface LaunchConversationDataListener {
- void onGetOrCreateNewConversation(String conversationId);
- void onGetOrCreateNewConversationFailed();
- }
-
- private LaunchConversationDataListener mListener;
- private GetOrCreateConversationActionMonitor mMonitor;
-
- public LaunchConversationData(final LaunchConversationDataListener listener) {
- mListener = listener;
- }
-
- @Override
- protected void unregisterListeners() {
- mListener = null;
- if (mMonitor != null) {
- mMonitor.unregister();
- }
- mMonitor = null;
- }
-
- public void getOrCreateConversation(final BindingBase<LaunchConversationData> binding,
- final String[] recipients) {
- final String bindingId = binding.getBindingId();
-
- // Start a new conversation from the list of contacts.
- if (isBound(bindingId) && mMonitor == null) {
- mMonitor = GetOrCreateConversationAction.getOrCreateConversation(recipients,
- bindingId, this);
- }
- }
-
- @Override
- @RunsOnMainThread
- public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
- final Object data, final String conversationId) {
- Assert.isTrue(monitor == mMonitor);
- Assert.isTrue(conversationId != null);
-
- final String bindingId = (String) data;
- if (isBound(bindingId) && mListener != null) {
- mListener.onGetOrCreateNewConversation(conversationId);
- }
-
- mMonitor = null;
- }
-
- @Override
- @RunsOnMainThread
- public void onGetOrCreateConversationFailed(final ActionMonitor monitor,
- final Object data) {
- Assert.isTrue(monitor == mMonitor);
- final String bindingId = (String) data;
- if (isBound(bindingId) && mListener != null) {
- mListener.onGetOrCreateNewConversationFailed();
- }
- LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed");
- mMonitor = null;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/MediaPickerData.java b/src/com/android/messaging/datamodel/data/MediaPickerData.java
deleted file mode 100644
index b0c8bf7..0000000
--- a/src/com/android/messaging/datamodel/data/MediaPickerData.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.app.LoaderManager;
-import android.content.Context;
-import android.content.Loader;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-
-import com.android.messaging.datamodel.BoundCursorLoader;
-import com.android.messaging.datamodel.GalleryBoundCursorLoader;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.BuglePrefsKeys;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Services data needs for MediaPicker.
- */
-public class MediaPickerData extends BindableData {
- public interface MediaPickerDataListener {
- void onMediaPickerDataUpdated(MediaPickerData mediaPickerData, Object data, int loaderId);
- }
-
- private static final String BINDING_ID = "bindingId";
- private final Context mContext;
- private LoaderManager mLoaderManager;
- private final GalleryLoaderCallbacks mGalleryLoaderCallbacks;
- private MediaPickerDataListener mListener;
-
- public MediaPickerData(final Context context) {
- mContext = context;
- mGalleryLoaderCallbacks = new GalleryLoaderCallbacks();
- }
-
- public static final int GALLERY_IMAGE_LOADER = 1;
-
- /**
- * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
- */
- private class GalleryLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- switch (id) {
- case GALLERY_IMAGE_LOADER:
- return new GalleryBoundCursorLoader(bindingId, mContext);
-
- default:
- Assert.fail("Unknown loader id for gallery picker!");
- break;
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the media picker");
- }
- return null;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
- final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
- if (isBound(cursorLoader.getBindingId())) {
- switch (loader.getId()) {
- case GALLERY_IMAGE_LOADER:
- mListener.onMediaPickerDataUpdated(MediaPickerData.this, data,
- GALLERY_IMAGE_LOADER);
- break;
-
- default:
- Assert.fail("Unknown loader id for gallery picker!");
- break;
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the media picker");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoaderReset(final Loader<Cursor> loader) {
- final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
- if (isBound(cursorLoader.getBindingId())) {
- switch (loader.getId()) {
- case GALLERY_IMAGE_LOADER:
- mListener.onMediaPickerDataUpdated(MediaPickerData.this, null,
- GALLERY_IMAGE_LOADER);
- break;
-
- default:
- Assert.fail("Unknown loader id for media picker!");
- break;
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the media picker");
- }
- }
- }
-
-
-
- public void startLoader(final int loaderId, final BindingBase<MediaPickerData> binding,
- @Nullable Bundle args, final MediaPickerDataListener listener) {
- if (args == null) {
- args = new Bundle();
- }
- args.putString(BINDING_ID, binding.getBindingId());
- if (loaderId == GALLERY_IMAGE_LOADER) {
- mLoaderManager.initLoader(loaderId, args, mGalleryLoaderCallbacks).forceLoad();
- } else {
- Assert.fail("Unsupported loader id for media picker!");
- }
- mListener = listener;
- }
-
- public void destroyLoader(final int loaderId) {
- mLoaderManager.destroyLoader(loaderId);
- }
-
- public void init(final LoaderManager loaderManager) {
- mLoaderManager = loaderManager;
- }
-
- @Override
- protected void unregisterListeners() {
- // This could be null if we bind but the caller doesn't init the BindableData
- if (mLoaderManager != null) {
- mLoaderManager.destroyLoader(GALLERY_IMAGE_LOADER);
- mLoaderManager = null;
- }
- }
-
- /**
- * Gets the last selected chooser index, or -1 if no selection has been saved.
- */
- public int getSelectedChooserIndex() {
- return BuglePrefs.getApplicationPrefs().getInt(
- BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX,
- BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX_DEFAULT);
- }
-
- /**
- * Saves the selected media chooser index.
- * @param selectedIndex the selected media chooser index.
- */
- public void saveSelectedChooserIndex(final int selectedIndex) {
- BuglePrefs.getApplicationPrefs().putInt(BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX,
- selectedIndex);
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java b/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java
deleted file mode 100644
index 7de9166..0000000
--- a/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.graphics.Rect;
-import android.net.Uri;
-
-public class MediaPickerMessagePartData extends MessagePartData {
- private final Rect mStartRect;
-
- public MediaPickerMessagePartData(final Rect startRect, final String contentType,
- final Uri contentUri, final int width, final int height) {
- this(startRect, null /* messageText */, contentType, contentUri, width, height);
- }
-
- public MediaPickerMessagePartData(final Rect startRect, final String messageText,
- final String contentType, final Uri contentUri, final int width, final int height) {
- this(startRect, messageText, contentType, contentUri, width, height,
- false /*onlySingleAttachment*/);
- }
-
- public MediaPickerMessagePartData(final Rect startRect, final String contentType,
- final Uri contentUri, final int width, final int height,
- final boolean onlySingleAttachment) {
- this(startRect, null /* messageText */, contentType, contentUri, width, height,
- onlySingleAttachment);
- }
-
- public MediaPickerMessagePartData(final Rect startRect, final String messageText,
- final String contentType, final Uri contentUri, final int width, final int height,
- final boolean onlySingleAttachment) {
- super(messageText, contentType, contentUri, width, height, onlySingleAttachment);
- mStartRect = startRect;
- }
-
- /**
- * @return The starting rect to animate the attachment preview from in order to perform a smooth
- * transition
- */
- public Rect getStartRect() {
- return mStartRect;
- }
-
- /**
- * Modify the start rect of the attachment.
- */
- public void setStartRect(final Rect startRect) {
- mStartRect.set(startRect);
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/MessageData.java b/src/com/android/messaging/datamodel/data/MessageData.java
deleted file mode 100644
index a3698a9..0000000
--- a/src/com/android/messaging/datamodel/data/MessageData.java
+++ /dev/null
@@ -1,922 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteStatement;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.OsUtil;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class MessageData implements Parcelable {
- private static final String[] sProjection = {
- MessageColumns._ID,
- MessageColumns.CONVERSATION_ID,
- MessageColumns.SENDER_PARTICIPANT_ID,
- MessageColumns.SELF_PARTICIPANT_ID,
- MessageColumns.SENT_TIMESTAMP,
- MessageColumns.RECEIVED_TIMESTAMP,
- MessageColumns.SEEN,
- MessageColumns.READ,
- MessageColumns.PROTOCOL,
- MessageColumns.STATUS,
- MessageColumns.SMS_MESSAGE_URI,
- MessageColumns.SMS_PRIORITY,
- MessageColumns.SMS_MESSAGE_SIZE,
- MessageColumns.MMS_SUBJECT,
- MessageColumns.MMS_TRANSACTION_ID,
- MessageColumns.MMS_CONTENT_LOCATION,
- MessageColumns.MMS_EXPIRY,
- MessageColumns.RAW_TELEPHONY_STATUS,
- MessageColumns.RETRY_START_TIMESTAMP,
- };
-
- private static final int INDEX_ID = 0;
- private static final int INDEX_CONVERSATION_ID = 1;
- private static final int INDEX_PARTICIPANT_ID = 2;
- private static final int INDEX_SELF_ID = 3;
- private static final int INDEX_SENT_TIMESTAMP = 4;
- private static final int INDEX_RECEIVED_TIMESTAMP = 5;
- private static final int INDEX_SEEN = 6;
- private static final int INDEX_READ = 7;
- private static final int INDEX_PROTOCOL = 8;
- private static final int INDEX_BUGLE_STATUS = 9;
- private static final int INDEX_SMS_MESSAGE_URI = 10;
- private static final int INDEX_SMS_PRIORITY = 11;
- private static final int INDEX_SMS_MESSAGE_SIZE = 12;
- private static final int INDEX_MMS_SUBJECT = 13;
- private static final int INDEX_MMS_TRANSACTION_ID = 14;
- private static final int INDEX_MMS_CONTENT_LOCATION = 15;
- private static final int INDEX_MMS_EXPIRY = 16;
- private static final int INDEX_RAW_TELEPHONY_STATUS = 17;
- private static final int INDEX_RETRY_START_TIMESTAMP = 18;
-
- // SQL statement to insert a "complete" message row (columns based on the projection above).
- private static final String INSERT_MESSAGE_SQL =
- "INSERT INTO " + DatabaseHelper.MESSAGES_TABLE + " ( "
- + TextUtils.join(", ", Arrays.copyOfRange(sProjection, 1,
- INDEX_RETRY_START_TIMESTAMP + 1))
- + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
-
- private String mMessageId;
- private String mConversationId;
- private String mParticipantId;
- private String mSelfId;
- private long mSentTimestamp;
- private long mReceivedTimestamp;
- private boolean mSeen;
- private boolean mRead;
- private int mProtocol;
- private Uri mSmsMessageUri;
- private int mSmsPriority;
- private long mSmsMessageSize;
- private String mMmsSubject;
- private String mMmsTransactionId;
- private String mMmsContentLocation;
- private long mMmsExpiry;
- private int mRawStatus;
- private int mStatus;
- private final ArrayList<MessagePartData> mParts;
- private long mRetryStartTimestamp;
-
- // PROTOCOL Values
- public static final int PROTOCOL_UNKNOWN = -1; // Unknown type
- public static final int PROTOCOL_SMS = 0; // SMS message
- public static final int PROTOCOL_MMS = 1; // MMS message
- public static final int PROTOCOL_MMS_PUSH_NOTIFICATION = 2; // MMS WAP push notification
-
- // Bugle STATUS Values
- public static final int BUGLE_STATUS_UNKNOWN = 0;
-
- // Outgoing
- public static final int BUGLE_STATUS_OUTGOING_COMPLETE = 1;
- public static final int BUGLE_STATUS_OUTGOING_DELIVERED = 2;
- // Transitions to either YET_TO_SEND or SEND_AFTER_PROCESSING depending attachments.
- public static final int BUGLE_STATUS_OUTGOING_DRAFT = 3;
- public static final int BUGLE_STATUS_OUTGOING_YET_TO_SEND = 4;
- public static final int BUGLE_STATUS_OUTGOING_SENDING = 5;
- public static final int BUGLE_STATUS_OUTGOING_RESENDING = 6;
- public static final int BUGLE_STATUS_OUTGOING_AWAITING_RETRY = 7;
- public static final int BUGLE_STATUS_OUTGOING_FAILED = 8;
- public static final int BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER = 9;
-
- // Incoming
- public static final int BUGLE_STATUS_INCOMING_COMPLETE = 100;
- public static final int BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD = 101;
- public static final int BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD = 102;
- public static final int BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING = 103;
- public static final int BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD = 104;
- public static final int BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING = 105;
- public static final int BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED = 106;
- public static final int BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE = 107;
-
- public static final String getStatusDescription(int status) {
- switch (status) {
- case BUGLE_STATUS_UNKNOWN:
- return "UNKNOWN";
- case BUGLE_STATUS_OUTGOING_COMPLETE:
- return "OUTGOING_COMPLETE";
- case BUGLE_STATUS_OUTGOING_DELIVERED:
- return "OUTGOING_DELIVERED";
- case BUGLE_STATUS_OUTGOING_DRAFT:
- return "OUTGOING_DRAFT";
- case BUGLE_STATUS_OUTGOING_YET_TO_SEND:
- return "OUTGOING_YET_TO_SEND";
- case BUGLE_STATUS_OUTGOING_SENDING:
- return "OUTGOING_SENDING";
- case BUGLE_STATUS_OUTGOING_RESENDING:
- return "OUTGOING_RESENDING";
- case BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
- return "OUTGOING_AWAITING_RETRY";
- case BUGLE_STATUS_OUTGOING_FAILED:
- return "OUTGOING_FAILED";
- case BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
- return "OUTGOING_FAILED_EMERGENCY_NUMBER";
- case BUGLE_STATUS_INCOMING_COMPLETE:
- return "INCOMING_COMPLETE";
- case BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
- return "INCOMING_YET_TO_MANUAL_DOWNLOAD";
- case BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
- return "INCOMING_RETRYING_MANUAL_DOWNLOAD";
- case BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
- return "INCOMING_MANUAL_DOWNLOADING";
- case BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
- return "INCOMING_RETRYING_AUTO_DOWNLOAD";
- case BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
- return "INCOMING_AUTO_DOWNLOADING";
- case BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
- return "INCOMING_DOWNLOAD_FAILED";
- case BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
- return "INCOMING_EXPIRED_OR_NOT_AVAILABLE";
- default:
- return String.valueOf(status) + " (check MessageData)";
- }
- }
-
- // All incoming messages expect to have status >= BUGLE_STATUS_FIRST_INCOMING
- public static final int BUGLE_STATUS_FIRST_INCOMING = BUGLE_STATUS_INCOMING_COMPLETE;
-
- // Detailed MMS failures. Most of the values are defined in PduHeaders. However, a few are
- // defined here instead. These are never returned in the MMS HTTP response, but are used
- // internally. The values here must not conflict with any of the existing PduHeader values.
- public static final int RAW_TELEPHONY_STATUS_UNDEFINED = MmsUtils.PDU_HEADER_VALUE_UNDEFINED;
- public static final int RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG = 10000;
-
- // Unknown result code for MMS sending/downloading. This is used as the default value
- // for result code returned from platform MMS API.
- public static final int UNKNOWN_RESULT_CODE = 0;
-
- /**
- * Create an "empty" message
- */
- public MessageData() {
- mParts = new ArrayList<MessagePartData>();
- }
-
- public static String[] getProjection() {
- return sProjection;
- }
-
- /**
- * Create a draft message for a particular conversation based on supplied content
- */
- public static MessageData createDraftMessage(final String conversationId,
- final String selfId, final MessageData content) {
- final MessageData message = new MessageData();
- message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
- message.mProtocol = PROTOCOL_UNKNOWN;
- message.mConversationId = conversationId;
- message.mParticipantId = selfId;
- message.mReceivedTimestamp = System.currentTimeMillis();
- if (content == null) {
- message.mParts.add(MessagePartData.createTextMessagePart(""));
- } else {
- if (!TextUtils.isEmpty(content.mParticipantId)) {
- message.mParticipantId = content.mParticipantId;
- }
- if (!TextUtils.isEmpty(content.mMmsSubject)) {
- message.mMmsSubject = content.mMmsSubject;
- }
- for (final MessagePartData part : content.getParts()) {
- message.mParts.add(part);
- }
- }
- message.mSelfId = selfId;
- return message;
- }
-
- /**
- * Create a draft sms message for a particular conversation
- */
- public static MessageData createDraftSmsMessage(final String conversationId,
- final String selfId, final String messageText) {
- final MessageData message = new MessageData();
- message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
- message.mProtocol = PROTOCOL_SMS;
- message.mConversationId = conversationId;
- message.mParticipantId = selfId;
- message.mSelfId = selfId;
- message.mParts.add(MessagePartData.createTextMessagePart(messageText));
- message.mReceivedTimestamp = System.currentTimeMillis();
- return message;
- }
-
- /**
- * Create a draft mms message for a particular conversation
- */
- public static MessageData createDraftMmsMessage(final String conversationId,
- final String selfId, final String messageText, final String subjectText) {
- final MessageData message = new MessageData();
- message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
- message.mProtocol = PROTOCOL_MMS;
- message.mConversationId = conversationId;
- message.mParticipantId = selfId;
- message.mSelfId = selfId;
- message.mMmsSubject = subjectText;
- message.mReceivedTimestamp = System.currentTimeMillis();
- if (!TextUtils.isEmpty(messageText)) {
- message.mParts.add(MessagePartData.createTextMessagePart(messageText));
- }
- return message;
- }
-
- /**
- * Create a message received from a particular number in a particular conversation
- */
- public static MessageData createReceivedSmsMessage(final Uri uri, final String conversationId,
- final String participantId, final String selfId, final String messageText,
- final String subject, final long sent, final long recieved,
- final boolean seen, final boolean read) {
- final MessageData message = new MessageData();
- message.mSmsMessageUri = uri;
- message.mConversationId = conversationId;
- message.mParticipantId = participantId;
- message.mSelfId = selfId;
- message.mProtocol = PROTOCOL_SMS;
- message.mStatus = BUGLE_STATUS_INCOMING_COMPLETE;
- message.mMmsSubject = subject;
- message.mReceivedTimestamp = recieved;
- message.mSentTimestamp = sent;
- message.mParts.add(MessagePartData.createTextMessagePart(messageText));
- message.mSeen = seen;
- message.mRead = read;
- return message;
- }
-
- /**
- * Create a message not yet associated with a particular conversation
- */
- public static MessageData createSharedMessage(final String messageText) {
- final MessageData message = new MessageData();
- message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
- if (!TextUtils.isEmpty(messageText)) {
- message.mParts.add(MessagePartData.createTextMessagePart(messageText));
- }
- return message;
- }
-
- /**
- * Create a message from Sms table fields
- */
- public static MessageData createSmsMessage(final String messageUri, final String participantId,
- final String selfId, final String conversationId, final int bugleStatus,
- final boolean seen, final boolean read, final long sent,
- final long recieved, final String messageText) {
- final MessageData message = new MessageData();
- message.mParticipantId = participantId;
- message.mSelfId = selfId;
- message.mConversationId = conversationId;
- message.mSentTimestamp = sent;
- message.mReceivedTimestamp = recieved;
- message.mSeen = seen;
- message.mRead = read;
- message.mProtocol = PROTOCOL_SMS;
- message.mStatus = bugleStatus;
- message.mSmsMessageUri = Uri.parse(messageUri);
- message.mParts.add(MessagePartData.createTextMessagePart(messageText));
- return message;
- }
-
- /**
- * Create a message from Mms table fields
- */
- public static MessageData createMmsMessage(final String messageUri, final String participantId,
- final String selfId, final String conversationId, final boolean isNotification,
- final int bugleStatus, final String contentLocation, final String transactionId,
- final int smsPriority, final String subject, final boolean seen, final boolean read,
- final long size, final int rawStatus, final long expiry, final long sent,
- final long received) {
- final MessageData message = new MessageData();
- message.mParticipantId = participantId;
- message.mSelfId = selfId;
- message.mConversationId = conversationId;
- message.mSentTimestamp = sent;
- message.mReceivedTimestamp = received;
- message.mMmsContentLocation = contentLocation;
- message.mMmsTransactionId = transactionId;
- message.mSeen = seen;
- message.mRead = read;
- message.mStatus = bugleStatus;
- message.mProtocol = (isNotification ? PROTOCOL_MMS_PUSH_NOTIFICATION : PROTOCOL_MMS);
- message.mSmsMessageUri = Uri.parse(messageUri);
- message.mSmsPriority = smsPriority;
- message.mSmsMessageSize = size;
- message.mMmsSubject = subject;
- message.mMmsExpiry = expiry;
- message.mRawStatus = rawStatus;
- if (bugleStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD ||
- bugleStatus == BUGLE_STATUS_OUTGOING_RESENDING) {
- // Set the retry start timestamp if this message is already in process of retrying
- // Either as autodownload is starting or sending already in progress (MMS update)
- message.mRetryStartTimestamp = received;
- }
- return message;
- }
-
- public void addPart(final MessagePartData part) {
- if (part instanceof PendingAttachmentData) {
- // Pending attachments may only be added to shared message data that's not associated
- // with any particular conversation, in order to store shared images.
- Assert.isTrue(mConversationId == null);
- }
- mParts.add(part);
- }
-
- public Iterable<MessagePartData> getParts() {
- return mParts;
- }
-
- public void bind(final Cursor cursor) {
- mMessageId = cursor.getString(INDEX_ID);
- mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
- mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
- mSelfId = cursor.getString(INDEX_SELF_ID);
- mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP);
- mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
- mSeen = (cursor.getInt(INDEX_SEEN) != 0);
- mRead = (cursor.getInt(INDEX_READ) != 0);
- mProtocol = cursor.getInt(INDEX_PROTOCOL);
- mStatus = cursor.getInt(INDEX_BUGLE_STATUS);
- final String smsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
- mSmsMessageUri = (smsMessageUri == null) ? null : Uri.parse(smsMessageUri);
- mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
- mSmsMessageSize = cursor.getLong(INDEX_SMS_MESSAGE_SIZE);
- mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
- mRawStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
- mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
- mMmsTransactionId = cursor.getString(INDEX_MMS_TRANSACTION_ID);
- mMmsContentLocation = cursor.getString(INDEX_MMS_CONTENT_LOCATION);
- mRetryStartTimestamp = cursor.getLong(INDEX_RETRY_START_TIMESTAMP);
- }
-
- /**
- * Bind to the draft message data for a conversation. The conversation's self id is used as
- * the draft's self id.
- */
- public void bindDraft(final Cursor cursor, final String conversationSelfId) {
- bind(cursor);
- mSelfId = conversationSelfId;
- }
-
- protected static String getParticipantId(final Cursor cursor) {
- return cursor.getString(INDEX_PARTICIPANT_ID);
- }
-
- public void populate(final ContentValues values) {
- values.put(MessageColumns.CONVERSATION_ID, mConversationId);
- values.put(MessageColumns.SENDER_PARTICIPANT_ID, mParticipantId);
- values.put(MessageColumns.SELF_PARTICIPANT_ID, mSelfId);
- values.put(MessageColumns.SENT_TIMESTAMP, mSentTimestamp);
- values.put(MessageColumns.RECEIVED_TIMESTAMP, mReceivedTimestamp);
- values.put(MessageColumns.SEEN, mSeen ? 1 : 0);
- values.put(MessageColumns.READ, mRead ? 1 : 0);
- values.put(MessageColumns.PROTOCOL, mProtocol);
- values.put(MessageColumns.STATUS, mStatus);
- final String smsMessageUri = ((mSmsMessageUri == null) ? null : mSmsMessageUri.toString());
- values.put(MessageColumns.SMS_MESSAGE_URI, smsMessageUri);
- values.put(MessageColumns.SMS_PRIORITY, mSmsPriority);
- values.put(MessageColumns.SMS_MESSAGE_SIZE, mSmsMessageSize);
- values.put(MessageColumns.MMS_EXPIRY, mMmsExpiry);
- values.put(MessageColumns.MMS_SUBJECT, mMmsSubject);
- values.put(MessageColumns.MMS_TRANSACTION_ID, mMmsTransactionId);
- values.put(MessageColumns.MMS_CONTENT_LOCATION, mMmsContentLocation);
- values.put(MessageColumns.RAW_TELEPHONY_STATUS, mRawStatus);
- values.put(MessageColumns.RETRY_START_TIMESTAMP, mRetryStartTimestamp);
- }
-
- /**
- * Note this is not thread safe so callers need to make sure they own the wrapper + statements
- * while they call this and use the returned value.
- */
- public SQLiteStatement getInsertStatement(final DatabaseWrapper db) {
- final SQLiteStatement insert = db.getStatementInTransaction(
- DatabaseWrapper.INDEX_INSERT_MESSAGE, INSERT_MESSAGE_SQL);
- insert.clearBindings();
- insert.bindString(INDEX_CONVERSATION_ID, mConversationId);
- insert.bindString(INDEX_PARTICIPANT_ID, mParticipantId);
- insert.bindString(INDEX_SELF_ID, mSelfId);
- insert.bindLong(INDEX_SENT_TIMESTAMP, mSentTimestamp);
- insert.bindLong(INDEX_RECEIVED_TIMESTAMP, mReceivedTimestamp);
- insert.bindLong(INDEX_SEEN, mSeen ? 1 : 0);
- insert.bindLong(INDEX_READ, mRead ? 1 : 0);
- insert.bindLong(INDEX_PROTOCOL, mProtocol);
- insert.bindLong(INDEX_BUGLE_STATUS, mStatus);
- if (mSmsMessageUri != null) {
- insert.bindString(INDEX_SMS_MESSAGE_URI, mSmsMessageUri.toString());
- }
- insert.bindLong(INDEX_SMS_PRIORITY, mSmsPriority);
- insert.bindLong(INDEX_SMS_MESSAGE_SIZE, mSmsMessageSize);
- insert.bindLong(INDEX_MMS_EXPIRY, mMmsExpiry);
- if (mMmsSubject != null) {
- insert.bindString(INDEX_MMS_SUBJECT, mMmsSubject);
- }
- if (mMmsTransactionId != null) {
- insert.bindString(INDEX_MMS_TRANSACTION_ID, mMmsTransactionId);
- }
- if (mMmsContentLocation != null) {
- insert.bindString(INDEX_MMS_CONTENT_LOCATION, mMmsContentLocation);
- }
- insert.bindLong(INDEX_RAW_TELEPHONY_STATUS, mRawStatus);
- insert.bindLong(INDEX_RETRY_START_TIMESTAMP, mRetryStartTimestamp);
- return insert;
- }
-
- public final String getMessageId() {
- return mMessageId;
- }
-
- public final String getConversationId() {
- return mConversationId;
- }
-
- public final String getParticipantId() {
- return mParticipantId;
- }
-
- public final String getSelfId() {
- return mSelfId;
- }
-
- public final long getSentTimeStamp() {
- return mSentTimestamp;
- }
-
- public final long getReceivedTimeStamp() {
- return mReceivedTimestamp;
- }
-
- public final String getFormattedReceivedTimeStamp() {
- return Dates.getMessageTimeString(mReceivedTimestamp).toString();
- }
-
- public final int getProtocol() {
- return mProtocol;
- }
-
- public final int getStatus() {
- return mStatus;
- }
-
- public final Uri getSmsMessageUri() {
- return mSmsMessageUri;
- }
-
- public final int getSmsPriority() {
- return mSmsPriority;
- }
-
- public final long getSmsMessageSize() {
- return mSmsMessageSize;
- }
-
- public final String getMmsSubject() {
- return mMmsSubject;
- }
-
- public final void setMmsSubject(final String subject) {
- mMmsSubject = subject;
- }
-
- public final String getMmsContentLocation() {
- return mMmsContentLocation;
- }
-
- public final String getMmsTransactionId() {
- return mMmsTransactionId;
- }
-
- public final boolean getMessageSeen() {
- return mSeen;
- }
-
- /**
- * For incoming MMS messages this returns the retrieve-status value
- * For sent MMS messages this returns the response-status value
- * See PduHeaders.java for possible values
- * Otherwise (SMS etc) this is RAW_TELEPHONY_STATUS_UNDEFINED
- */
- public final int getRawTelephonyStatus() {
- return mRawStatus;
- }
-
- public final void setMessageSeen(final boolean hasSeen) {
- mSeen = hasSeen;
- }
-
- public final boolean getInResendWindow(final long now) {
- final long maxAgeToResend = BugleGservices.get().getLong(
- BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS,
- BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS_DEFAULT);
- final long age = now - mRetryStartTimestamp;
- return age < maxAgeToResend;
- }
-
- public final boolean getInDownloadWindow(final long now) {
- final long maxAgeToRedownload = BugleGservices.get().getLong(
- BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS,
- BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT);
- final long age = now - mRetryStartTimestamp;
- return age < maxAgeToRedownload;
- }
-
- static boolean getShowDownloadMessage(final int status) {
- if (OsUtil.isSecondaryUser()) {
- // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
- // primary user.
- return false;
- }
- // Should show option for manual download iff status is manual download or failed
- return (status == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
- status == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
- // If debug is enabled, allow to download an expired or unavailable message.
- (DebugUtils.isDebugEnabled()
- && status == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
- }
-
- public boolean canDownloadMessage() {
- if (OsUtil.isSecondaryUser()) {
- // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
- // primary user.
- return false;
- }
- // Can download iff status is retrying auto/manual downloading
- return (mStatus == BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD ||
- mStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD);
- }
-
- public boolean canRedownloadMessage() {
- if (OsUtil.isSecondaryUser()) {
- // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
- // primary user.
- return false;
- }
- // Can redownload iff status is manual download not started or download failed
- return (mStatus == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
- mStatus == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
- // If debug is enabled, allow to download an expired or unavailable message.
- (DebugUtils.isDebugEnabled()
- && mStatus == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
- }
-
- static boolean getShowResendMessage(final int status) {
- // Should show option to resend iff status is failed
- return (status == BUGLE_STATUS_OUTGOING_FAILED);
- }
-
- static boolean getOneClickResendMessage(final int status, final int rawStatus) {
- // Should show option to resend iff status is failed
- return (status == BUGLE_STATUS_OUTGOING_FAILED
- && rawStatus == RAW_TELEPHONY_STATUS_UNDEFINED);
- }
-
- public boolean canResendMessage() {
- // Manual retry allowed only from failed
- return (mStatus == BUGLE_STATUS_OUTGOING_FAILED);
- }
-
- public boolean canSendMessage() {
- // Sending messages must be in yet_to_send or awaiting_retry state
- return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND ||
- mStatus == BUGLE_STATUS_OUTGOING_AWAITING_RETRY);
- }
-
- public final boolean getYetToSend() {
- return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND);
- }
-
- public final boolean getIsMms() {
- return mProtocol == MessageData.PROTOCOL_MMS
- || mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION;
- }
-
- public static final boolean getIsMmsNotification(final int protocol) {
- return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
- }
-
- public final boolean getIsMmsNotification() {
- return getIsMmsNotification(mProtocol);
- }
-
- public static final boolean getIsSms(final int protocol) {
- return protocol == (MessageData.PROTOCOL_SMS);
- }
-
- public final boolean getIsSms() {
- return getIsSms(mProtocol);
- }
-
- public static boolean getIsIncoming(final int status) {
- return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
- }
-
- public boolean getIsIncoming() {
- return getIsIncoming(mStatus);
- }
-
- public long getRetryStartTimestamp() {
- return mRetryStartTimestamp;
- }
-
- public final String getMessageText() {
- final String separator = System.getProperty("line.separator");
- final StringBuilder text = new StringBuilder();
- for (final MessagePartData part : mParts) {
- if (!part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
- if (text.length() > 0) {
- text.append(separator);
- }
- text.append(part.getText());
- }
- }
- return text.toString();
- }
-
- /**
- * Takes all captions from attachments and adds them as a prefix to the first text part or
- * appends a text part
- */
- public final void consolidateText() {
- final String separator = System.getProperty("line.separator");
- final StringBuilder captionText = new StringBuilder();
- MessagePartData firstTextPart = null;
- int firstTextPartIndex = -1;
- for (int i = 0; i < mParts.size(); i++) {
- final MessagePartData part = mParts.get(i);
- if (firstTextPart == null && !part.isAttachment()) {
- firstTextPart = part;
- firstTextPartIndex = i;
- }
- if (part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
- if (captionText.length() > 0) {
- captionText.append(separator);
- }
- captionText.append(part.getText());
- }
- }
-
- if (captionText.length() == 0) {
- // Nothing to consolidate
- return;
- }
-
- if (firstTextPart == null) {
- addPart(MessagePartData.createTextMessagePart(captionText.toString()));
- } else {
- final String partText = firstTextPart.getText();
- if (partText.length() > 0) {
- captionText.append(separator);
- captionText.append(partText);
- }
- mParts.set(firstTextPartIndex,
- MessagePartData.createTextMessagePart(captionText.toString()));
- }
- }
-
- public final MessagePartData getFirstAttachment() {
- for (final MessagePartData part : mParts) {
- if (part.isAttachment()) {
- return part;
- }
- }
- return null;
- }
-
- /**
- * Updates the messageId for this message.
- * Can be used to reset the messageId prior to persisting (which will assign a new messageId)
- * or can be called on a message that does not yet have a valid messageId to set it.
- */
- public void updateMessageId(final String messageId) {
- Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId));
- mMessageId = messageId;
-
- // TODO : This should probably also call updateMessageId on the message parts. We
- // may also want to make messages effectively immutable once they have a valid message id.
- }
-
- public final void updateSendingMessage(final String conversationId, final Uri messageUri,
- final long timestamp) {
- mConversationId = conversationId;
- mSmsMessageUri = messageUri;
- mRead = true;
- mSeen = true;
- mReceivedTimestamp = timestamp;
- mSentTimestamp = timestamp;
- mStatus = BUGLE_STATUS_OUTGOING_YET_TO_SEND;
- mRetryStartTimestamp = timestamp;
- }
-
- public final void markMessageManualResend(final long timestamp) {
- // Manual send updates timestamp and transitions back to initial sending status.
- mReceivedTimestamp = timestamp;
- mSentTimestamp = timestamp;
- mStatus = BUGLE_STATUS_OUTGOING_SENDING;
- }
-
- public final void markMessageSending(final long timestamp) {
- // Initial send
- mStatus = BUGLE_STATUS_OUTGOING_SENDING;
- mSentTimestamp = timestamp;
- }
-
- public final void markMessageResending(final long timestamp) {
- // Auto resend of message
- mStatus = BUGLE_STATUS_OUTGOING_RESENDING;
- mSentTimestamp = timestamp;
- }
-
- public final void markMessageSent(final long timestamp) {
- mSentTimestamp = timestamp;
- mStatus = BUGLE_STATUS_OUTGOING_COMPLETE;
- }
-
- public final void markMessageFailed(final long timestamp) {
- mSentTimestamp = timestamp;
- mStatus = BUGLE_STATUS_OUTGOING_FAILED;
- }
-
- public final void markMessageFailedEmergencyNumber(final long timestamp) {
- mSentTimestamp = timestamp;
- mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
- }
-
- public final void markMessageNotSent(final long timestamp) {
- mSentTimestamp = timestamp;
- mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY;
- }
-
- public final void updateSizesForImageParts() {
- for (final MessagePartData part : getParts()) {
- part.decodeAndSaveSizeIfImage(false /* saveToStorage */);
- }
- }
-
- public final void setRetryStartTimestamp(final long timestamp) {
- mRetryStartTimestamp = timestamp;
- }
-
- public final void setRawTelephonyStatus(final int rawStatus) {
- mRawStatus = rawStatus;
- }
-
- public boolean hasContent() {
- return !TextUtils.isEmpty(mMmsSubject) ||
- getFirstAttachment() != null ||
- !TextUtils.isEmpty(getMessageText());
- }
-
- public final void bindSelfId(final String selfId) {
- mSelfId = selfId;
- }
-
- public final void bindParticipantId(final String participantId) {
- mParticipantId = participantId;
- }
-
- protected MessageData(final Parcel in) {
- mMessageId = in.readString();
- mConversationId = in.readString();
- mParticipantId = in.readString();
- mSelfId = in.readString();
- mSentTimestamp = in.readLong();
- mReceivedTimestamp = in.readLong();
- mSeen = (in.readInt() != 0);
- mRead = (in.readInt() != 0);
- mProtocol = in.readInt();
- mStatus = in.readInt();
- final String smsMessageUri = in.readString();
- mSmsMessageUri = (smsMessageUri == null ? null : Uri.parse(smsMessageUri));
- mSmsPriority = in.readInt();
- mSmsMessageSize = in.readLong();
- mMmsExpiry = in.readLong();
- mMmsSubject = in.readString();
- mMmsTransactionId = in.readString();
- mMmsContentLocation = in.readString();
- mRawStatus = in.readInt();
- mRetryStartTimestamp = in.readLong();
-
- // Read parts
- mParts = new ArrayList<MessagePartData>();
- final int partCount = in.readInt();
- for (int i = 0; i < partCount; i++) {
- mParts.add((MessagePartData) in.readParcelable(MessagePartData.class.getClassLoader()));
- }
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeString(mMessageId);
- dest.writeString(mConversationId);
- dest.writeString(mParticipantId);
- dest.writeString(mSelfId);
- dest.writeLong(mSentTimestamp);
- dest.writeLong(mReceivedTimestamp);
- dest.writeInt(mRead ? 1 : 0);
- dest.writeInt(mSeen ? 1 : 0);
- dest.writeInt(mProtocol);
- dest.writeInt(mStatus);
- final String smsMessageUri = (mSmsMessageUri == null) ? null : mSmsMessageUri.toString();
- dest.writeString(smsMessageUri);
- dest.writeInt(mSmsPriority);
- dest.writeLong(mSmsMessageSize);
- dest.writeLong(mMmsExpiry);
- dest.writeString(mMmsSubject);
- dest.writeString(mMmsTransactionId);
- dest.writeString(mMmsContentLocation);
- dest.writeInt(mRawStatus);
- dest.writeLong(mRetryStartTimestamp);
-
- // Write parts
- dest.writeInt(mParts.size());
- for (final MessagePartData messagePartData : mParts) {
- dest.writeParcelable(messagePartData, flags);
- }
- }
-
- public static final Parcelable.Creator<MessageData> CREATOR
- = new Parcelable.Creator<MessageData>() {
- @Override
- public MessageData createFromParcel(final Parcel in) {
- return new MessageData(in);
- }
-
- @Override
- public MessageData[] newArray(final int size) {
- return new MessageData[size];
- }
- };
-
- @Override
- public String toString() {
- return toString(mMessageId, mParts);
- }
-
- public static String toString(String messageId, List<MessagePartData> parts) {
- StringBuilder sb = new StringBuilder();
- if (messageId != null) {
- sb.append(messageId);
- sb.append(": ");
- }
- for (MessagePartData part : parts) {
- sb.append(part.toString());
- sb.append(" ");
- }
- return sb.toString();
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/MessagePartData.java b/src/com/android/messaging/datamodel/data/MessagePartData.java
deleted file mode 100644
index fffaca8..0000000
--- a/src/com/android/messaging/datamodel/data/MessagePartData.java
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteStatement;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.action.UpdateMessagePartSizeAction;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.GifTranscoder;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UriUtil;
-
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents a single message part. Messages consist of one or more parts which may contain
- * either text or media.
- */
-public class MessagePartData implements Parcelable {
- public static final int UNSPECIFIED_SIZE = MessagingContentProvider.UNSPECIFIED_SIZE;
- public static final String[] ACCEPTABLE_IMAGE_TYPES =
- new String[] { ContentType.IMAGE_JPEG, ContentType.IMAGE_JPG, ContentType.IMAGE_PNG,
- ContentType.IMAGE_GIF };
-
- private static final String[] sProjection = {
- PartColumns._ID,
- PartColumns.MESSAGE_ID,
- PartColumns.TEXT,
- PartColumns.CONTENT_URI,
- PartColumns.CONTENT_TYPE,
- PartColumns.WIDTH,
- PartColumns.HEIGHT,
- };
-
- private static final int INDEX_ID = 0;
- private static final int INDEX_MESSAGE_ID = 1;
- private static final int INDEX_TEXT = 2;
- private static final int INDEX_CONTENT_URI = 3;
- private static final int INDEX_CONTENT_TYPE = 4;
- private static final int INDEX_WIDTH = 5;
- private static final int INDEX_HEIGHT = 6;
- // This isn't part of the projection
- private static final int INDEX_CONVERSATION_ID = 7;
-
- // SQL statement to insert a "complete" message part row (columns based on projection above).
- private static final String INSERT_MESSAGE_PART_SQL =
- "INSERT INTO " + DatabaseHelper.PARTS_TABLE + " ( "
- + TextUtils.join(",", Arrays.copyOfRange(sProjection, 1, INDEX_CONVERSATION_ID))
- + ", " + PartColumns.CONVERSATION_ID
- + ") VALUES (?, ?, ?, ?, ?, ?, ?)";
-
- // Used for stuff that's ignored or arbitrarily compressed.
- private static final long NO_MINIMUM_SIZE = 0;
-
- private String mPartId;
- private String mMessageId;
- private String mText;
- private Uri mContentUri;
- private String mContentType;
- private int mWidth;
- private int mHeight;
- // This kind of part can only be attached once and with no other attachment
- private boolean mSinglePartOnly;
-
- /** Transient data: true if destroy was already called */
- private boolean mDestroyed;
-
- /**
- * Create an "empty" message part
- */
- protected MessagePartData() {
- this(null, null, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE);
- }
-
- /**
- * Create a populated text message part
- */
- protected MessagePartData(final String messageText) {
- this(null, messageText, ContentType.TEXT_PLAIN, null, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE,
- false /*singlePartOnly*/);
- }
-
- /**
- * Create a populated attachment message part
- */
- protected MessagePartData(final String contentType, final Uri contentUri,
- final int width, final int height) {
- this(null, null, contentType, contentUri, width, height, false /*singlePartOnly*/);
- }
-
- /**
- * Create a populated attachment message part, with additional caption text
- */
- protected MessagePartData(final String messageText, final String contentType,
- final Uri contentUri, final int width, final int height) {
- this(null, messageText, contentType, contentUri, width, height, false /*singlePartOnly*/);
- }
-
- /**
- * Create a populated attachment message part, with additional caption text, single part only
- */
- protected MessagePartData(final String messageText, final String contentType,
- final Uri contentUri, final int width, final int height, final boolean singlePartOnly) {
- this(null, messageText, contentType, contentUri, width, height, singlePartOnly);
- }
-
- /**
- * Create a populated message part
- */
- private MessagePartData(final String messageId, final String messageText,
- final String contentType, final Uri contentUri, final int width, final int height,
- final boolean singlePartOnly) {
- mMessageId = messageId;
- mText = messageText;
- mContentType = contentType;
- mContentUri = contentUri;
- mWidth = width;
- mHeight = height;
- mSinglePartOnly = singlePartOnly;
- }
-
- /**
- * Create a "text" message part
- */
- public static MessagePartData createTextMessagePart(final String messageText) {
- return new MessagePartData(messageText);
- }
-
- /**
- * Create a "media" message part
- */
- public static MessagePartData createMediaMessagePart(final String contentType,
- final Uri contentUri, final int width, final int height) {
- return new MessagePartData(contentType, contentUri, width, height);
- }
-
- /**
- * Create a "media" message part with caption
- */
- public static MessagePartData createMediaMessagePart(final String caption,
- final String contentType, final Uri contentUri, final int width, final int height) {
- return new MessagePartData(null, caption, contentType, contentUri, width, height,
- false /*singlePartOnly*/
- );
- }
-
- /**
- * Create an empty "text" message part
- */
- public static MessagePartData createEmptyMessagePart() {
- return new MessagePartData("");
- }
-
- /**
- * Creates a new message part reading from the cursor
- */
- public static MessagePartData createFromCursor(final Cursor cursor) {
- final MessagePartData part = new MessagePartData();
- part.bind(cursor);
- return part;
- }
-
- public static String[] getProjection() {
- return sProjection;
- }
-
- /**
- * Updates the part id.
- * Can be used to reset the partId just prior to persisting (which will assign a new partId)
- * or can be called on a part that does not yet have a valid part id to set it.
- */
- public void updatePartId(final String partId) {
- Assert.isTrue(TextUtils.isEmpty(partId) || TextUtils.isEmpty(mPartId));
- mPartId = partId;
- }
-
- /**
- * Updates the messageId for the part.
- * Can be used to reset the messageId prior to persisting (which will assign a new messageId)
- * or can be called on a part that does not yet have a valid messageId to set it.
- */
- public void updateMessageId(final String messageId) {
- Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId));
- mMessageId = messageId;
- }
-
- protected static String getMessageId(final Cursor cursor) {
- return cursor.getString(INDEX_MESSAGE_ID);
- }
-
- protected void bind(final Cursor cursor) {
- mPartId = cursor.getString(INDEX_ID);
- mMessageId = cursor.getString(INDEX_MESSAGE_ID);
- mText = cursor.getString(INDEX_TEXT);
- mContentUri = UriUtil.uriFromString(cursor.getString(INDEX_CONTENT_URI));
- mContentType = cursor.getString(INDEX_CONTENT_TYPE);
- mWidth = cursor.getInt(INDEX_WIDTH);
- mHeight = cursor.getInt(INDEX_HEIGHT);
- }
-
- public final void populate(final ContentValues values) {
- // Must have a valid messageId on a part
- Assert.isTrue(!TextUtils.isEmpty(mMessageId));
- values.put(PartColumns.MESSAGE_ID, mMessageId);
- values.put(PartColumns.TEXT, mText);
- values.put(PartColumns.CONTENT_URI, UriUtil.stringFromUri(mContentUri));
- values.put(PartColumns.CONTENT_TYPE, mContentType);
- if (mWidth != UNSPECIFIED_SIZE) {
- values.put(PartColumns.WIDTH, mWidth);
- }
- if (mHeight != UNSPECIFIED_SIZE) {
- values.put(PartColumns.HEIGHT, mHeight);
- }
- }
-
- /**
- * Note this is not thread safe so callers need to make sure they own the wrapper + statements
- * while they call this and use the returned value.
- */
- public SQLiteStatement getInsertStatement(final DatabaseWrapper db,
- final String conversationId) {
- final SQLiteStatement insert = db.getStatementInTransaction(
- DatabaseWrapper.INDEX_INSERT_MESSAGE_PART, INSERT_MESSAGE_PART_SQL);
- insert.clearBindings();
- insert.bindString(INDEX_MESSAGE_ID, mMessageId);
- if (mText != null) {
- insert.bindString(INDEX_TEXT, mText);
- }
- if (mContentUri != null) {
- insert.bindString(INDEX_CONTENT_URI, mContentUri.toString());
- }
- if (mContentType != null) {
- insert.bindString(INDEX_CONTENT_TYPE, mContentType);
- }
- insert.bindLong(INDEX_WIDTH, mWidth);
- insert.bindLong(INDEX_HEIGHT, mHeight);
- insert.bindString(INDEX_CONVERSATION_ID, conversationId);
- return insert;
- }
-
- public final String getPartId() {
- return mPartId;
- }
-
- public final String getMessageId() {
- return mMessageId;
- }
-
- public final String getText() {
- return mText;
- }
-
- public final Uri getContentUri() {
- return mContentUri;
- }
-
- public boolean isAttachment() {
- return mContentUri != null;
- }
-
- public boolean isText() {
- return ContentType.isTextType(mContentType);
- }
-
- public boolean isImage() {
- return ContentType.isImageType(mContentType);
- }
-
- public boolean isMedia() {
- return ContentType.isMediaType(mContentType);
- }
-
- public boolean isVCard() {
- return ContentType.isVCardType(mContentType);
- }
-
- public boolean isAudio() {
- return ContentType.isAudioType(mContentType);
- }
-
- public boolean isVideo() {
- return ContentType.isVideoType(mContentType);
- }
-
- public final String getContentType() {
- return mContentType;
- }
-
- public final int getWidth() {
- return mWidth;
- }
-
- public final int getHeight() {
- return mHeight;
- }
-
- /**
- *
- * @return true if this part can only exist by itself, with no other attachments
- */
- public boolean getSinglePartOnly() {
- return mSinglePartOnly;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- protected MessagePartData(final Parcel in) {
- mMessageId = in.readString();
- mText = in.readString();
- mContentUri = UriUtil.uriFromString(in.readString());
- mContentType = in.readString();
- mWidth = in.readInt();
- mHeight = in.readInt();
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- Assert.isTrue(!mDestroyed);
- dest.writeString(mMessageId);
- dest.writeString(mText);
- dest.writeString(UriUtil.stringFromUri(mContentUri));
- dest.writeString(mContentType);
- dest.writeInt(mWidth);
- dest.writeInt(mHeight);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof MessagePartData)) {
- return false;
- }
-
- MessagePartData lhs = (MessagePartData) o;
- return mWidth == lhs.mWidth && mHeight == lhs.mHeight &&
- TextUtils.equals(mMessageId, lhs.mMessageId) &&
- TextUtils.equals(mText, lhs.mText) &&
- TextUtils.equals(mContentType, lhs.mContentType) &&
- (mContentUri == null ? lhs.mContentUri == null
- : mContentUri.equals(lhs.mContentUri));
- }
-
- @Override public int hashCode() {
- int result = 17;
- result = 31 * result + mWidth;
- result = 31 * result + mHeight;
- result = 31 * result + (mMessageId == null ? 0 : mMessageId.hashCode());
- result = 31 * result + (mText == null ? 0 : mText.hashCode());
- result = 31 * result + (mContentType == null ? 0 : mContentType.hashCode());
- result = 31 * result + (mContentUri == null ? 0 : mContentUri.hashCode());
- return result;
- }
-
- public static final Parcelable.Creator<MessagePartData> CREATOR
- = new Parcelable.Creator<MessagePartData>() {
- @Override
- public MessagePartData createFromParcel(final Parcel in) {
- return new MessagePartData(in);
- }
-
- @Override
- public MessagePartData[] newArray(final int size) {
- return new MessagePartData[size];
- }
- };
-
- protected Uri shouldDestroy() {
- // We should never double-destroy.
- Assert.isTrue(!mDestroyed);
- mDestroyed = true;
- Uri contentUri = mContentUri;
- mContentUri = null;
- mContentType = null;
- // Only destroy the image if it's staged in our scratch space.
- if (!MediaScratchFileProvider.isMediaScratchSpaceUri(contentUri)) {
- contentUri = null;
- }
- return contentUri;
- }
-
- /**
- * If application owns content associated with this part delete it (on background thread)
- */
- public void destroyAsync() {
- final Uri contentUri = shouldDestroy();
- if (contentUri != null) {
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- Factory.get().getApplicationContext().getContentResolver().delete(
- contentUri, null, null);
- }
- });
- }
- }
-
- /**
- * If application owns content associated with this part delete it
- */
- public void destroySync() {
- final Uri contentUri = shouldDestroy();
- if (contentUri != null) {
- Factory.get().getApplicationContext().getContentResolver().delete(
- contentUri, null, null);
- }
- }
-
- /**
- * If this is an image part, decode the image header and potentially save the size to the db.
- */
- public void decodeAndSaveSizeIfImage(final boolean saveToStorage) {
- if (isImage()) {
- final Rect imageSize = ImageUtils.decodeImageBounds(
- Factory.get().getApplicationContext(), mContentUri);
- if (imageSize.width() != ImageRequest.UNSPECIFIED_SIZE &&
- imageSize.height() != ImageRequest.UNSPECIFIED_SIZE) {
- mWidth = imageSize.width();
- mHeight = imageSize.height();
- if (saveToStorage) {
- UpdateMessagePartSizeAction.updateSize(mPartId, mWidth, mHeight);
- }
- }
- }
- }
-
- /**
- * Computes the minimum size that this MessagePartData could be compressed/downsampled/encoded
- * before sending to meet the maximum message size imposed by the carriers. This is used to
- * determine right before sending a message whether a message could possibly be sent. If not
- * then the user is given a chance to unselect some/all of the attachments.
- *
- * TODO: computing the minimum size could be expensive. Should we cache the
- * computed value in db to be retrieved later?
- *
- * @return the carrier-independent minimum size, in bytes.
- */
- @DoesNotRunOnMainThread
- public long getMinimumSizeInBytesForSending() {
- Assert.isNotMainThread();
- if (!isAttachment()) {
- // No limit is imposed on non-attachment part (i.e. plain text), so treat it as zero.
- return NO_MINIMUM_SIZE;
- } else if (isImage()) {
- // GIFs are resized by the native transcoder (exposed by GifTranscoder).
- if (ImageUtils.isGif(mContentType, mContentUri)) {
- final long originalImageSize = UriUtil.getContentSize(mContentUri);
- // Wish we could save the size here, but we don't have a part id yet
- decodeAndSaveSizeIfImage(false /* saveToStorage */);
- return GifTranscoder.canBeTranscoded(mWidth, mHeight) ?
- GifTranscoder.estimateFileSizeAfterTranscode(originalImageSize)
- : originalImageSize;
- }
- // Other images should be arbitrarily resized by ImageResizer before sending.
- return MmsUtils.MIN_IMAGE_BYTE_SIZE;
- } else if (isAudio()) {
- // Audios are already recorded with the lowest sampling settings (AMR_NB), so just
- // return the file size as the minimum size.
- return UriUtil.getContentSize(mContentUri);
- } else if (isVideo()) {
- final int mediaDurationMs = UriUtil.getMediaDurationMs(mContentUri);
- return MmsUtils.MIN_VIDEO_BYTES_PER_SECOND * mediaDurationMs
- / TimeUnit.SECONDS.toMillis(1);
- } else if (isVCard()) {
- // We can't compress vCards.
- return UriUtil.getContentSize(mContentUri);
- } else {
- // This is some unknown media type that we don't know how to handle. Log an error
- // and try sending it anyway.
- LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Unknown attachment type " + getContentType());
- return NO_MINIMUM_SIZE;
- }
- }
-
- @Override
- public String toString() {
- if (isText()) {
- return LogUtil.sanitizePII(getText());
- } else {
- return getContentType() + " (" + getContentUri() + ")";
- }
- }
-
- /**
- *
- * @return true if this part can only exist by itself, with no other attachments
- */
- public boolean isSinglePartOnly() {
- return mSinglePartOnly;
- }
-
- public void setSinglePartOnly(final boolean isSinglePartOnly) {
- mSinglePartOnly = isSinglePartOnly;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/ParticipantData.java b/src/com/android/messaging/datamodel/data/ParticipantData.java
deleted file mode 100644
index 521c354..0000000
--- a/src/com/android/messaging/datamodel/data/ParticipantData.java
+++ /dev/null
@@ -1,569 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.content.ContentValues;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.v7.mms.MmsManager;
-import android.telephony.SubscriptionInfo;
-import android.text.TextUtils;
-
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.TextUtil;
-
-/**
- * A class that encapsulates all of the data for a specific participant in a conversation.
- */
-public class ParticipantData implements Parcelable {
- // We always use -1 as default/invalid sub id although system may give us anything negative
- public static final int DEFAULT_SELF_SUB_ID = MmsManager.DEFAULT_SUB_ID;
-
- // This needs to be something apart from valid or DEFAULT_SELF_SUB_ID
- public static final int OTHER_THAN_SELF_SUB_ID = DEFAULT_SELF_SUB_ID - 1;
-
- // Active slot ids are non-negative. Using -1 to designate to inactive self participants.
- public static final int INVALID_SLOT_ID = -1;
-
- // TODO: may make sense to move this to common place?
- public static final long PARTICIPANT_CONTACT_ID_NOT_RESOLVED = -1;
- public static final long PARTICIPANT_CONTACT_ID_NOT_FOUND = -2;
-
- public static class ParticipantsQuery {
- public static final String[] PROJECTION = new String[] {
- ParticipantColumns._ID,
- ParticipantColumns.SUB_ID,
- ParticipantColumns.SIM_SLOT_ID,
- ParticipantColumns.NORMALIZED_DESTINATION,
- ParticipantColumns.SEND_DESTINATION,
- ParticipantColumns.DISPLAY_DESTINATION,
- ParticipantColumns.FULL_NAME,
- ParticipantColumns.FIRST_NAME,
- ParticipantColumns.PROFILE_PHOTO_URI,
- ParticipantColumns.CONTACT_ID,
- ParticipantColumns.LOOKUP_KEY,
- ParticipantColumns.BLOCKED,
- ParticipantColumns.SUBSCRIPTION_COLOR,
- ParticipantColumns.SUBSCRIPTION_NAME,
- ParticipantColumns.CONTACT_DESTINATION,
- };
-
- public static final int INDEX_ID = 0;
- public static final int INDEX_SUB_ID = 1;
- public static final int INDEX_SIM_SLOT_ID = 2;
- public static final int INDEX_NORMALIZED_DESTINATION = 3;
- public static final int INDEX_SEND_DESTINATION = 4;
- public static final int INDEX_DISPLAY_DESTINATION = 5;
- public static final int INDEX_FULL_NAME = 6;
- public static final int INDEX_FIRST_NAME = 7;
- public static final int INDEX_PROFILE_PHOTO_URI = 8;
- public static final int INDEX_CONTACT_ID = 9;
- public static final int INDEX_LOOKUP_KEY = 10;
- public static final int INDEX_BLOCKED = 11;
- public static final int INDEX_SUBSCRIPTION_COLOR = 12;
- public static final int INDEX_SUBSCRIPTION_NAME = 13;
- public static final int INDEX_CONTACT_DESTINATION = 14;
- }
-
- /**
- * @return The MMS unknown sender participant entity
- */
- public static String getUnknownSenderDestination() {
- // This is a hard coded string rather than a localized one because we don't want it to
- // change when you change locale.
- return "\u02BCUNKNOWN_SENDER!\u02BC";
- }
-
- private String mParticipantId;
- private int mSubId;
- private int mSlotId;
- private String mNormalizedDestination;
- private String mSendDestination;
- private String mDisplayDestination;
- private String mContactDestination;
- private String mFullName;
- private String mFirstName;
- private String mProfilePhotoUri;
- private long mContactId;
- private String mLookupKey;
- private int mSubscriptionColor;
- private String mSubscriptionName;
- private boolean mIsEmailAddress;
- private boolean mBlocked;
-
- // Don't call constructor directly
- private ParticipantData() {
- }
-
- public static ParticipantData getFromCursor(final Cursor cursor) {
- final ParticipantData pd = new ParticipantData();
- pd.mParticipantId = cursor.getString(ParticipantsQuery.INDEX_ID);
- pd.mSubId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID);
- pd.mSlotId = cursor.getInt(ParticipantsQuery.INDEX_SIM_SLOT_ID);
- pd.mNormalizedDestination = cursor.getString(
- ParticipantsQuery.INDEX_NORMALIZED_DESTINATION);
- pd.mSendDestination = cursor.getString(ParticipantsQuery.INDEX_SEND_DESTINATION);
- pd.mDisplayDestination = cursor.getString(ParticipantsQuery.INDEX_DISPLAY_DESTINATION);
- pd.mContactDestination = cursor.getString(ParticipantsQuery.INDEX_CONTACT_DESTINATION);
- pd.mFullName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME);
- pd.mFirstName = cursor.getString(ParticipantsQuery.INDEX_FIRST_NAME);
- pd.mProfilePhotoUri = cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI);
- pd.mContactId = cursor.getLong(ParticipantsQuery.INDEX_CONTACT_ID);
- pd.mLookupKey = cursor.getString(ParticipantsQuery.INDEX_LOOKUP_KEY);
- pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
- pd.mBlocked = cursor.getInt(ParticipantsQuery.INDEX_BLOCKED) != 0;
- pd.mSubscriptionColor = cursor.getInt(ParticipantsQuery.INDEX_SUBSCRIPTION_COLOR);
- pd.mSubscriptionName = cursor.getString(ParticipantsQuery.INDEX_SUBSCRIPTION_NAME);
- pd.maybeSetupUnknownSender();
- return pd;
- }
-
- public static ParticipantData getFromId(final DatabaseWrapper dbWrapper,
- final String participantId) {
- Cursor cursor = null;
- try {
- cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
- ParticipantsQuery.PROJECTION,
- ParticipantColumns._ID + " =?",
- new String[] { participantId }, null, null, null);
-
- if (cursor.moveToFirst()) {
- return ParticipantData.getFromCursor(cursor);
- } else {
- return null;
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- public static ParticipantData getFromRecipientEntry(final RecipientEntry recipientEntry) {
- final ParticipantData pd = new ParticipantData();
- pd.mParticipantId = null;
- pd.mSubId = OTHER_THAN_SELF_SUB_ID;
- pd.mSlotId = INVALID_SLOT_ID;
- pd.mSendDestination = TextUtil.replaceUnicodeDigits(recipientEntry.getDestination());
- pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
- pd.mNormalizedDestination = pd.mIsEmailAddress ?
- pd.mSendDestination :
- PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
- pd.mDisplayDestination = pd.mIsEmailAddress ?
- pd.mNormalizedDestination :
- PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
- pd.mFullName = recipientEntry.getDisplayName();
- pd.mFirstName = null;
- pd.mProfilePhotoUri = (recipientEntry.getPhotoThumbnailUri() == null) ? null :
- recipientEntry.getPhotoThumbnailUri().toString();
- pd.mContactId = recipientEntry.getContactId();
- if (pd.mContactId < 0) {
- // ParticipantData only supports real contact ids (>=0) based on faith that the contacts
- // provider will continue to only use non-negative ids. The UI uses contactId < 0 for
- // special handling. We convert those to 'not resolved'
- pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
- }
- pd.mLookupKey = recipientEntry.getLookupKey();
- pd.mBlocked = false;
- pd.mSubscriptionColor = Color.TRANSPARENT;
- pd.mSubscriptionName = null;
- pd.maybeSetupUnknownSender();
- return pd;
- }
-
- // Shared code for getFromRawPhoneBySystemLocale and getFromRawPhoneBySimLocale
- private static ParticipantData getFromRawPhone(final String phoneNumber) {
- Assert.isTrue(phoneNumber != null);
- final ParticipantData pd = new ParticipantData();
- pd.mParticipantId = null;
- pd.mSubId = OTHER_THAN_SELF_SUB_ID;
- pd.mSlotId = INVALID_SLOT_ID;
- pd.mSendDestination = TextUtil.replaceUnicodeDigits(phoneNumber);
- pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
- pd.mFullName = null;
- pd.mFirstName = null;
- pd.mProfilePhotoUri = null;
- pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
- pd.mLookupKey = null;
- pd.mBlocked = false;
- pd.mSubscriptionColor = Color.TRANSPARENT;
- pd.mSubscriptionName = null;
- return pd;
- }
-
- /**
- * Get an instance from a raw phone number and using system locale to normalize it.
- *
- * Use this when creating a participant that is for displaying UI and not associated
- * with a specific SIM. For example, when creating a conversation using user entered
- * phone number.
- *
- * @param phoneNumber The raw phone number
- * @return instance
- */
- public static ParticipantData getFromRawPhoneBySystemLocale(final String phoneNumber) {
- final ParticipantData pd = getFromRawPhone(phoneNumber);
- pd.mNormalizedDestination = pd.mIsEmailAddress ?
- pd.mSendDestination :
- PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
- pd.mDisplayDestination = pd.mIsEmailAddress ?
- pd.mNormalizedDestination :
- PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
- pd.maybeSetupUnknownSender();
- return pd;
- }
-
- /**
- * Get an instance from a raw phone number and using SIM or system locale to normalize it.
- *
- * Use this when creating a participant that is associated with a specific SIM. For example,
- * the sender of a received message or the recipient of a sending message that is already
- * targeted at a specific SIM.
- *
- * @param phoneNumber The raw phone number
- * @return instance
- */
- public static ParticipantData getFromRawPhoneBySimLocale(
- final String phoneNumber, final int subId) {
- final ParticipantData pd = getFromRawPhone(phoneNumber);
- pd.mNormalizedDestination = pd.mIsEmailAddress ?
- pd.mSendDestination :
- PhoneUtils.get(subId).getCanonicalBySimLocale(pd.mSendDestination);
- pd.mDisplayDestination = pd.mIsEmailAddress ?
- pd.mNormalizedDestination :
- PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
- pd.maybeSetupUnknownSender();
- return pd;
- }
-
- public static ParticipantData getSelfParticipant(final int subId) {
- Assert.isTrue(subId != OTHER_THAN_SELF_SUB_ID);
- final ParticipantData pd = new ParticipantData();
- pd.mParticipantId = null;
- pd.mSubId = subId;
- pd.mSlotId = INVALID_SLOT_ID;
- pd.mIsEmailAddress = false;
- pd.mSendDestination = null;
- pd.mNormalizedDestination = null;
- pd.mDisplayDestination = null;
- pd.mFullName = null;
- pd.mFirstName = null;
- pd.mProfilePhotoUri = null;
- pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
- pd.mLookupKey = null;
- pd.mBlocked = false;
- pd.mSubscriptionColor = Color.TRANSPARENT;
- pd.mSubscriptionName = null;
- return pd;
- }
-
- private void maybeSetupUnknownSender() {
- if (isUnknownSender()) {
- // Because your locale may change, we setup the display string for the unknown sender
- // on the fly rather than relying on the version in the database.
- final Resources resources = Factory.get().getApplicationContext().getResources();
- mDisplayDestination = resources.getString(R.string.unknown_sender);
- mFullName = mDisplayDestination;
- }
- }
-
- public String getNormalizedDestination() {
- return mNormalizedDestination;
- }
-
- public String getSendDestination() {
- return mSendDestination;
- }
-
- public String getDisplayDestination() {
- return mDisplayDestination;
- }
-
- public String getContactDestination() {
- return mContactDestination;
- }
-
- public String getFullName() {
- return mFullName;
- }
-
- public String getFirstName() {
- return mFirstName;
- }
-
- public String getDisplayName(final boolean preferFullName) {
- if (preferFullName) {
- // Prefer full name over first name
- if (!TextUtils.isEmpty(mFullName)) {
- return mFullName;
- }
- if (!TextUtils.isEmpty(mFirstName)) {
- return mFirstName;
- }
- } else {
- // Prefer first name over full name
- if (!TextUtils.isEmpty(mFirstName)) {
- return mFirstName;
- }
- if (!TextUtils.isEmpty(mFullName)) {
- return mFullName;
- }
- }
-
- // Fallback to the display destination
- if (!TextUtils.isEmpty(mDisplayDestination)) {
- return mDisplayDestination;
- }
-
- return Factory.get().getApplicationContext().getResources().getString(
- R.string.unknown_sender);
- }
-
- public String getProfilePhotoUri() {
- return mProfilePhotoUri;
- }
-
- public long getContactId() {
- return mContactId;
- }
-
- public String getLookupKey() {
- return mLookupKey;
- }
-
- public boolean updatePhoneNumberForSelfIfChanged() {
- final String phoneNumber =
- PhoneUtils.get(mSubId).getCanonicalForSelf(true/*allowOverride*/);
- boolean changed = false;
- if (isSelf() && !TextUtils.equals(phoneNumber, mNormalizedDestination)) {
- mNormalizedDestination = phoneNumber;
- mSendDestination = phoneNumber;
- mDisplayDestination = mIsEmailAddress ?
- phoneNumber :
- PhoneUtils.getDefault().formatForDisplay(phoneNumber);
- changed = true;
- }
- return changed;
- }
-
- public boolean updateSubscriptionInfoForSelfIfChanged(final SubscriptionInfo subscriptionInfo) {
- boolean changed = false;
- if (isSelf()) {
- if (subscriptionInfo == null) {
- // The subscription is inactive. Check if the participant is still active.
- if (isActiveSubscription()) {
- mSlotId = INVALID_SLOT_ID;
- mSubscriptionColor = Color.TRANSPARENT;
- mSubscriptionName = "";
- changed = true;
- }
- } else {
- final int slotId = subscriptionInfo.getSimSlotIndex();
- final int color = subscriptionInfo.getIconTint();
- final CharSequence name = subscriptionInfo.getDisplayName();
- if (mSlotId != slotId || mSubscriptionColor != color || mSubscriptionName != name) {
- mSlotId = slotId;
- mSubscriptionColor = color;
- mSubscriptionName = name.toString();
- changed = true;
- }
- }
- }
- return changed;
- }
-
- public void setFullName(final String fullName) {
- mFullName = fullName;
- }
-
- public void setFirstName(final String firstName) {
- mFirstName = firstName;
- }
-
- public void setProfilePhotoUri(final String profilePhotoUri) {
- mProfilePhotoUri = profilePhotoUri;
- }
-
- public void setContactId(final long contactId) {
- mContactId = contactId;
- }
-
- public void setLookupKey(final String lookupKey) {
- mLookupKey = lookupKey;
- }
-
- public void setSendDestination(final String destination) {
- mSendDestination = destination;
- }
-
- public void setContactDestination(final String destination) {
- mContactDestination = destination;
- }
-
- public int getSubId() {
- return mSubId;
- }
-
- /**
- * @return whether this sub is active. Note that {@link ParticipantData#DEFAULT_SELF_SUB_ID} is
- * is considered as active if there is any active SIM.
- */
- public boolean isActiveSubscription() {
- return mSlotId != INVALID_SLOT_ID;
- }
-
- public boolean isDefaultSelf() {
- return mSubId == ParticipantData.DEFAULT_SELF_SUB_ID;
- }
-
- public int getSlotId() {
- return mSlotId;
- }
-
- /**
- * Slot IDs in the subscription manager is zero-based, but we want to show it
- * as 1-based in UI.
- */
- public int getDisplaySlotId() {
- return getSlotId() + 1;
- }
-
- public int getSubscriptionColor() {
- Assert.isTrue(isActiveSubscription());
- // Force the alpha channel to 0xff to ensure the returned color is solid.
- return mSubscriptionColor | 0xff000000;
- }
-
- public String getSubscriptionName() {
- Assert.isTrue(isActiveSubscription());
- return mSubscriptionName;
- }
-
- public String getId() {
- return mParticipantId;
- }
-
- public boolean isSelf() {
- return (mSubId != OTHER_THAN_SELF_SUB_ID);
- }
-
- public boolean isEmail() {
- return mIsEmailAddress;
- }
-
- public boolean isContactIdResolved() {
- return (mContactId != PARTICIPANT_CONTACT_ID_NOT_RESOLVED);
- }
-
- public boolean isBlocked() {
- return mBlocked;
- }
-
- public boolean isUnknownSender() {
- final String unknownSender = ParticipantData.getUnknownSenderDestination();
- return (TextUtils.equals(mSendDestination, unknownSender));
- }
-
- public ContentValues toContentValues() {
- final ContentValues values = new ContentValues();
- values.put(ParticipantColumns.SUB_ID, mSubId);
- values.put(ParticipantColumns.SIM_SLOT_ID, mSlotId);
- values.put(DatabaseHelper.ParticipantColumns.SEND_DESTINATION, mSendDestination);
-
- if (!isUnknownSender()) {
- values.put(DatabaseHelper.ParticipantColumns.DISPLAY_DESTINATION, mDisplayDestination);
- values.put(DatabaseHelper.ParticipantColumns.NORMALIZED_DESTINATION,
- mNormalizedDestination);
- values.put(ParticipantColumns.FULL_NAME, mFullName);
- values.put(ParticipantColumns.FIRST_NAME, mFirstName);
- }
-
- values.put(ParticipantColumns.PROFILE_PHOTO_URI, mProfilePhotoUri);
- values.put(ParticipantColumns.CONTACT_ID, mContactId);
- values.put(ParticipantColumns.LOOKUP_KEY, mLookupKey);
- values.put(ParticipantColumns.BLOCKED, mBlocked);
- values.put(ParticipantColumns.SUBSCRIPTION_COLOR, mSubscriptionColor);
- values.put(ParticipantColumns.SUBSCRIPTION_NAME, mSubscriptionName);
- return values;
- }
-
- public ParticipantData(final Parcel in) {
- mParticipantId = in.readString();
- mSubId = in.readInt();
- mSlotId = in.readInt();
- mNormalizedDestination = in.readString();
- mSendDestination = in.readString();
- mDisplayDestination = in.readString();
- mFullName = in.readString();
- mFirstName = in.readString();
- mProfilePhotoUri = in.readString();
- mContactId = in.readLong();
- mLookupKey = in.readString();
- mIsEmailAddress = in.readInt() != 0;
- mBlocked = in.readInt() != 0;
- mSubscriptionColor = in.readInt();
- mSubscriptionName = in.readString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeString(mParticipantId);
- dest.writeInt(mSubId);
- dest.writeInt(mSlotId);
- dest.writeString(mNormalizedDestination);
- dest.writeString(mSendDestination);
- dest.writeString(mDisplayDestination);
- dest.writeString(mFullName);
- dest.writeString(mFirstName);
- dest.writeString(mProfilePhotoUri);
- dest.writeLong(mContactId);
- dest.writeString(mLookupKey);
- dest.writeInt(mIsEmailAddress ? 1 : 0);
- dest.writeInt(mBlocked ? 1 : 0);
- dest.writeInt(mSubscriptionColor);
- dest.writeString(mSubscriptionName);
- }
-
- public static final Parcelable.Creator<ParticipantData> CREATOR
- = new Parcelable.Creator<ParticipantData>() {
- @Override
- public ParticipantData createFromParcel(final Parcel in) {
- return new ParticipantData(in);
- }
-
- @Override
- public ParticipantData[] newArray(final int size) {
- return new ParticipantData[size];
- }
- };
-}
diff --git a/src/com/android/messaging/datamodel/data/ParticipantListItemData.java b/src/com/android/messaging/datamodel/data/ParticipantListItemData.java
deleted file mode 100644
index f6c9b5f..0000000
--- a/src/com/android/messaging/datamodel/data/ParticipantListItemData.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.data;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.action.BugleActionToasts;
-import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
-import com.android.messaging.util.AvatarUriUtil;
-
-/**
- * Helps visualize a ParticipantData in a PersonItemView
- */
-public class ParticipantListItemData extends PersonItemData {
- private final Uri mAvatarUri;
- private final String mDisplayName;
- private final String mDetails;
- private final long mContactId;
- private final String mLookupKey;
- private final String mNormalizedDestination;
-
- /**
- * Constructor. Takes necessary info from the incoming ParticipantData.
- */
- public ParticipantListItemData(final ParticipantData participant) {
- mAvatarUri = AvatarUriUtil.createAvatarUri(participant);
- mContactId = participant.getContactId();
- mLookupKey = participant.getLookupKey();
- mNormalizedDestination = participant.getNormalizedDestination();
- if (TextUtils.isEmpty(participant.getFullName())) {
- mDisplayName = participant.getSendDestination();
- mDetails = null;
- } else {
- mDisplayName = participant.getFullName();
- mDetails = (participant.isUnknownSender()) ? null : participant.getSendDestination();
- }
- }
-
- @Override
- public Uri getAvatarUri() {
- return mAvatarUri;
- }
-
- @Override
- public String getDisplayName() {
- return mDisplayName;
- }
-
- @Override
- public String getDetails() {
- return mDetails;
- }
-
- @Override
- public Intent getClickIntent() {
- return null;
- }
-
- @Override
- public long getContactId() {
- return mContactId;
- }
-
- @Override
- public String getLookupKey() {
- return mLookupKey;
- }
-
- @Override
- public String getNormalizedDestination() {
- return mNormalizedDestination;
- }
-
- public void unblock(final Context context) {
- UpdateDestinationBlockedAction.updateDestinationBlocked(
- mNormalizedDestination, false, null,
- BugleActionToasts.makeUpdateDestinationBlockedActionListener(context));
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/PendingAttachmentData.java b/src/com/android/messaging/datamodel/data/PendingAttachmentData.java
deleted file mode 100644
index 5e079f8..0000000
--- a/src/com/android/messaging/datamodel/data/PendingAttachmentData.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.data;
-
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.annotation.NonNull;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UriUtil;
-
-/**
- * Represents a "pending" message part that acts as a placeholder for the actual attachment being
- * loaded. It handles the task to load and persist the attachment from a Uri to local scratch
- * folder. This item is not persisted to the database.
- */
-public class PendingAttachmentData extends MessagePartData {
- /** The pending state. This is the initial state where we haven't started loading yet */
- public static final int STATE_PENDING = 0;
-
- /** The state for when we are currently loading the attachment to the scratch space */
- public static final int STATE_LOADING = 1;
-
- /** The attachment has been successfully loaded and no longer pending */
- public static final int STATE_LOADED = 2;
-
- /** The attachment failed to load */
- public static final int STATE_FAILED = 3;
-
- private static final int LOAD_MEDIA_TIME_LIMIT_MILLIS = 60 * 1000; // 60s
-
- /** The current state of the pending attachment. Refer to the STATE_* states above */
- private int mCurrentState;
-
- /**
- * Create a new instance of PendingAttachmentData with an output Uri.
- * @param sourceUri the source Uri of the attachment. The Uri maybe temporary or remote,
- * so we need to persist it to local storage.
- */
- protected PendingAttachmentData(final String caption, final String contentType,
- @NonNull final Uri sourceUri, final int width, final int height,
- final boolean onlySingleAttachment) {
- super(caption, contentType, sourceUri, width, height, onlySingleAttachment);
- mCurrentState = STATE_PENDING;
- }
-
- /**
- * Creates a pending attachment data that is able to load from the given source uri and
- * persist the media resource locally in the scratch folder.
- */
- public static PendingAttachmentData createPendingAttachmentData(final String contentType,
- final Uri sourceUri) {
- return createPendingAttachmentData(null, contentType, sourceUri, UNSPECIFIED_SIZE,
- UNSPECIFIED_SIZE);
- }
-
- public static PendingAttachmentData createPendingAttachmentData(final String caption,
- final String contentType, final Uri sourceUri, final int width, final int height) {
- Assert.isTrue(ContentType.isMediaType(contentType));
- return new PendingAttachmentData(caption, contentType, sourceUri, width, height,
- false /*onlySingleAttachment*/);
- }
-
- public static PendingAttachmentData createPendingAttachmentData(final String caption,
- final String contentType, final Uri sourceUri, final int width, final int height,
- final boolean onlySingleAttachment) {
- Assert.isTrue(ContentType.isMediaType(contentType));
- return new PendingAttachmentData(caption, contentType, sourceUri, width, height,
- onlySingleAttachment);
- }
-
- public int getCurrentState() {
- return mCurrentState;
- }
-
- public void loadAttachmentForDraft(final DraftMessageData draftMessageData,
- final String bindingId) {
- if (mCurrentState != STATE_PENDING) {
- return;
- }
- mCurrentState = STATE_LOADING;
-
- // Kick off a SafeAsyncTask to load the content of the media and persist it locally.
- // Note: we need to persist the media locally even if it's not remote, because we
- // want to be able to resend the media in case the message failed to send.
- new SafeAsyncTask<Void, Void, MessagePartData>(LOAD_MEDIA_TIME_LIMIT_MILLIS,
- true /* cancelExecutionOnTimeout */) {
- @Override
- protected MessagePartData doInBackgroundTimed(final Void... params) {
- final Uri contentUri = getContentUri();
- final Uri persistedUri = UriUtil.persistContentToScratchSpace(contentUri);
- if (persistedUri != null) {
- return MessagePartData.createMediaMessagePart(
- getText(),
- getContentType(),
- persistedUri,
- getWidth(),
- getHeight());
- }
- return null;
- }
-
- @Override
- protected void onCancelled() {
- LogUtil.w(LogUtil.BUGLE_TAG, "Timeout while retrieving media");
- mCurrentState = STATE_FAILED;
- if (draftMessageData.isBound(bindingId)) {
- draftMessageData.removePendingAttachment(PendingAttachmentData.this);
- }
- }
-
- @Override
- protected void onPostExecute(final MessagePartData attachment) {
- if (attachment != null) {
- mCurrentState = STATE_LOADED;
- if (draftMessageData.isBound(bindingId)) {
- draftMessageData.updatePendingAttachment(attachment,
- PendingAttachmentData.this);
- } else {
- // The draft message data is no longer bound, drop the loaded attachment.
- attachment.destroyAsync();
- }
- } else {
- // Media load failed. We already logged in doInBackground() so don't need to
- // do that again.
- mCurrentState = STATE_FAILED;
- if (draftMessageData.isBound(bindingId)) {
- draftMessageData.onPendingAttachmentLoadFailed(PendingAttachmentData.this);
- draftMessageData.removePendingAttachment(PendingAttachmentData.this);
- }
- }
- }
- }.executeOnThreadPool();
- }
-
- protected PendingAttachmentData(final Parcel in) {
- super(in);
- mCurrentState = in.readInt();
- }
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(mCurrentState);
- }
-
- public static final Parcelable.Creator<PendingAttachmentData> CREATOR
- = new Parcelable.Creator<PendingAttachmentData>() {
- @Override
- public PendingAttachmentData createFromParcel(final Parcel in) {
- return new PendingAttachmentData(in);
- }
-
- @Override
- public PendingAttachmentData[] newArray(final int size) {
- return new PendingAttachmentData[size];
- }
- };
-}
diff --git a/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java b/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java
deleted file mode 100644
index 650a037..0000000
--- a/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.app.LoaderManager;
-import android.content.Context;
-import android.content.Loader;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-
-import com.android.messaging.datamodel.BoundCursorLoader;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.action.BugleActionToasts;
-import com.android.messaging.datamodel.action.UpdateConversationOptionsAction;
-import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.List;
-
-/**
- * Services data needs for PeopleAndOptionsFragment.
- */
-public class PeopleAndOptionsData extends BindableData implements
- LoaderManager.LoaderCallbacks<Cursor> {
- public interface PeopleAndOptionsDataListener {
- void onOptionsCursorUpdated(PeopleAndOptionsData data, Cursor cursor);
- void onParticipantsListLoaded(PeopleAndOptionsData data,
- List<ParticipantData> participants);
- }
-
- private static final String BINDING_ID = "bindingId";
- private final Context mContext;
- private final String mConversationId;
- private final ConversationParticipantsData mParticipantData;
- private LoaderManager mLoaderManager;
- private PeopleAndOptionsDataListener mListener;
-
- public PeopleAndOptionsData(final String conversationId, final Context context,
- final PeopleAndOptionsDataListener listener) {
- mListener = listener;
- mContext = context;
- mConversationId = conversationId;
- mParticipantData = new ConversationParticipantsData();
- }
-
- private static final int CONVERSATION_OPTIONS_LOADER = 1;
- private static final int PARTICIPANT_LOADER = 2;
-
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- switch (id) {
- case CONVERSATION_OPTIONS_LOADER: {
- final Uri uri =
- MessagingContentProvider.buildConversationMetadataUri(mConversationId);
- return new BoundCursorLoader(bindingId, mContext, uri,
- PeopleOptionsItemData.PROJECTION, null, null, null);
- }
-
- case PARTICIPANT_LOADER: {
- final Uri uri =
- MessagingContentProvider
- .buildConversationParticipantsUri(mConversationId);
- return new BoundCursorLoader(bindingId, mContext, uri,
- ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
- }
-
- default:
- Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
- break;
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding PeopleAndOptionsFragment");
- }
- return null;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
- final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
- if (isBound(cursorLoader.getBindingId())) {
- switch (loader.getId()) {
- case CONVERSATION_OPTIONS_LOADER:
- mListener.onOptionsCursorUpdated(this, data);
- break;
-
- case PARTICIPANT_LOADER:
- mParticipantData.bind(data);
- mListener.onParticipantsListLoaded(this,
- mParticipantData.getParticipantListExcludingSelf());
- break;
-
- default:
- Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
- break;
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG,
- "Loader finished after unbinding PeopleAndOptionsFragment");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoaderReset(final Loader<Cursor> loader) {
- final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
- if (isBound(cursorLoader.getBindingId())) {
- switch (loader.getId()) {
- case CONVERSATION_OPTIONS_LOADER:
- mListener.onOptionsCursorUpdated(this, null);
- break;
-
- case PARTICIPANT_LOADER:
- mParticipantData.bind(null);
- break;
-
- default:
- Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
- break;
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding PeopleAndOptionsFragment");
- }
- }
-
- public void init(final LoaderManager loaderManager,
- final BindingBase<PeopleAndOptionsData> binding) {
- final Bundle args = new Bundle();
- args.putString(BINDING_ID, binding.getBindingId());
- mLoaderManager = loaderManager;
- mLoaderManager.initLoader(CONVERSATION_OPTIONS_LOADER, args, this);
- mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this);
- }
-
- @Override
- protected void unregisterListeners() {
- mListener = null;
-
- // This could be null if we bind but the caller doesn't init the BindableData
- if (mLoaderManager != null) {
- mLoaderManager.destroyLoader(CONVERSATION_OPTIONS_LOADER);
- mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
- mLoaderManager = null;
- }
- }
-
- public void enableConversationNotifications(final BindingBase<PeopleAndOptionsData> binding,
- final boolean enable) {
- final String bindingId = binding.getBindingId();
- if (isBound(bindingId)) {
- UpdateConversationOptionsAction.enableConversationNotifications(
- mConversationId, enable);
- }
- }
-
- public void setConversationNotificationSound(final BindingBase<PeopleAndOptionsData> binding,
- final String ringtoneUri) {
- final String bindingId = binding.getBindingId();
- if (isBound(bindingId)) {
- UpdateConversationOptionsAction.setConversationNotificationSound(mConversationId,
- ringtoneUri);
- }
- }
-
- public void enableConversationNotificationVibration(
- final BindingBase<PeopleAndOptionsData> binding, final boolean enable) {
- final String bindingId = binding.getBindingId();
- if (isBound(bindingId)) {
- UpdateConversationOptionsAction.enableVibrationForConversationNotification(
- mConversationId, enable);
- }
- }
-
- public void setDestinationBlocked(final BindingBase<PeopleAndOptionsData> binding,
- final boolean blocked) {
- final String bindingId = binding.getBindingId();
- final ParticipantData participantData = mParticipantData.getOtherParticipant();
- if (isBound(bindingId) && participantData != null) {
- UpdateDestinationBlockedAction.updateDestinationBlocked(
- participantData.getNormalizedDestination(),
- blocked, mConversationId,
- BugleActionToasts.makeUpdateDestinationBlockedActionListener(mContext));
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java b/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java
deleted file mode 100644
index 5af6a30..0000000
--- a/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.data;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.RingtoneUtil;
-
-public class PeopleOptionsItemData {
- public static final String[] PROJECTION = {
- ConversationListViewColumns.NOTIFICATION_ENABLED,
- ConversationListViewColumns.NOTIFICATION_SOUND_URI,
- ConversationListViewColumns.NOTIFICATION_VIBRATION,
- };
-
- // Column index for query projection.
- private static final int INDEX_NOTIFICATION_ENABLED = 0;
- private static final int INDEX_NOTIFICATION_SOUND_URI = 1;
- private static final int INDEX_NOTIFICATION_VIBRATION = 2;
-
- // Identification for each setting that's surfaced to the UI layer.
- public static final int SETTING_NOTIFICATION_ENABLED = 0;
- public static final int SETTING_NOTIFICATION_SOUND_URI = 1;
- public static final int SETTING_NOTIFICATION_VIBRATION = 2;
- public static final int SETTING_BLOCKED = 3;
- public static final int SETTINGS_COUNT = 4;
-
- // Type of UI switch to show for the toggle button.
- public static final int TOGGLE_TYPE_CHECKBOX = 0;
- public static final int TOGGLE_TYPE_SWITCH = 1;
-
- private String mTitle;
- private String mSubtitle;
- private Uri mRingtoneUri;
- private boolean mCheckable;
- private boolean mChecked;
- private boolean mEnabled;
- private int mItemId;
- private ParticipantData mOtherParticipant;
-
- private final Context mContext;
-
- public PeopleOptionsItemData(final Context context) {
- mContext = context;
- }
-
- /**
- * Bind to a specific setting column on conversation metadata cursor. (Note
- * that it binds to columns because it treats individual columns of the cursor as
- * separate options to display for the conversation, e.g. notification settings).
- */
- public void bind(
- final Cursor cursor, final ParticipantData otherParticipant, final int settingType) {
- mSubtitle = null;
- mRingtoneUri = null;
- mCheckable = true;
- mEnabled = true;
- mItemId = settingType;
- mOtherParticipant = otherParticipant;
-
- final boolean notificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1;
- switch (settingType) {
- case SETTING_NOTIFICATION_ENABLED:
- mTitle = mContext.getString(R.string.notifications_enabled_conversation_pref_title);
- mChecked = notificationEnabled;
- break;
-
- case SETTING_NOTIFICATION_SOUND_URI:
- mTitle = mContext.getString(R.string.notification_sound_pref_title);
- final String ringtoneString = cursor.getString(INDEX_NOTIFICATION_SOUND_URI);
- Uri ringtoneUri = RingtoneUtil.getNotificationRingtoneUri(ringtoneString);
-
- mSubtitle = mContext.getString(R.string.silent_ringtone);
- if (ringtoneUri != null) {
- final Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri);
- if (ringtone != null) {
- mSubtitle = ringtone.getTitle(mContext);
- }
- }
- mCheckable = false;
- mRingtoneUri = ringtoneUri;
- mEnabled = notificationEnabled;
- break;
-
- case SETTING_NOTIFICATION_VIBRATION:
- mTitle = mContext.getString(R.string.notification_vibrate_pref_title);
- mChecked = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1;
- mEnabled = notificationEnabled;
- break;
-
- case SETTING_BLOCKED:
- Assert.notNull(otherParticipant);
- final int resourceId = otherParticipant.isBlocked() ?
- R.string.unblock_contact_title : R.string.block_contact_title;
- mTitle = mContext.getString(resourceId, otherParticipant.getDisplayDestination());
- mCheckable = false;
- break;
-
- default:
- Assert.fail("Unsupported conversation option type!");
- }
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public String getSubtitle() {
- return mSubtitle;
- }
-
- public boolean getCheckable() {
- return mCheckable;
- }
-
- public boolean getChecked() {
- return mChecked;
- }
-
- public boolean getEnabled() {
- return mEnabled;
- }
-
- public int getItemId() {
- return mItemId;
- }
-
- public Uri getRingtoneUri() {
- return mRingtoneUri;
- }
-
- public ParticipantData getOtherParticipant() {
- return mOtherParticipant;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/PersonItemData.java b/src/com/android/messaging/datamodel/data/PersonItemData.java
deleted file mode 100644
index a0a1ce8..0000000
--- a/src/com/android/messaging/datamodel/data/PersonItemData.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.data;
-
-import android.content.Intent;
-import android.net.Uri;
-
-import com.android.messaging.datamodel.binding.BindableData;
-
-/**
- * Bridges between any particpant/contact related data and data displayed in the PersonItemView.
- */
-public abstract class PersonItemData extends BindableData {
- /**
- * The UI component that listens for data change and update accordingly.
- */
- public interface PersonItemDataListener {
- void onPersonDataUpdated(PersonItemData data);
- void onPersonDataFailed(PersonItemData data, Exception exception);
- }
-
- private PersonItemDataListener mListener;
-
- public abstract Uri getAvatarUri();
- public abstract String getDisplayName();
- public abstract String getDetails();
- public abstract Intent getClickIntent();
- public abstract long getContactId();
- public abstract String getLookupKey();
- public abstract String getNormalizedDestination();
-
- public void setListener(final PersonItemDataListener listener) {
- if (isBound()) {
- mListener = listener;
- }
- }
-
- protected void notifyDataUpdated() {
- if (isBound() && mListener != null) {
- mListener.onPersonDataUpdated(this);
- }
- }
-
- protected void notifyDataFailed(final Exception exception) {
- if (isBound() && mListener != null) {
- mListener.onPersonDataFailed(this, exception);
- }
- }
-
- @Override
- protected void unregisterListeners() {
- mListener = null;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/SelfParticipantsData.java b/src/com/android/messaging/datamodel/data/SelfParticipantsData.java
deleted file mode 100644
index 43302ed..0000000
--- a/src/com/android/messaging/datamodel/data/SelfParticipantsData.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.database.Cursor;
-import android.support.v4.util.ArrayMap;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.android.messaging.util.OsUtil;
-
-/**
- * A class that contains the list of all self participants potentially involved in a conversation.
- * This class contains both active/inactive self entries when there is multi-SIM support.
- */
-public class SelfParticipantsData {
- /**
- * The map from self participant ids to self-participant data entries in the participants table.
- * This includes both active, inactive and default (with subId ==
- * {@link ParticipantData#DEFAULT_SELF_SUB_ID}) subscriptions.
- */
- private final ArrayMap<String, ParticipantData> mSelfParticipantMap;
-
- public SelfParticipantsData() {
- mSelfParticipantMap = new ArrayMap<String, ParticipantData>();
- }
-
- public void bind(final Cursor cursor) {
- mSelfParticipantMap.clear();
- if (cursor != null) {
- while (cursor.moveToNext()) {
- final ParticipantData newParticipant = ParticipantData.getFromCursor(cursor);
- mSelfParticipantMap.put(newParticipant.getId(), newParticipant);
- }
- }
- }
-
- /**
- * Gets the list of self participants for all subscriptions.
- * @param activeOnly if set, returns active self entries only (i.e. those with SIMs plugged in).
- */
- public List<ParticipantData> getSelfParticipants(final boolean activeOnly) {
- List<ParticipantData> list = new ArrayList<ParticipantData>();
- for (final ParticipantData self : mSelfParticipantMap.values()) {
- if (!activeOnly || self.isActiveSubscription()) {
- list.add(self);
- }
- }
- return list;
- }
-
- /**
- * Gets the self participant corresponding to the given self id.
- */
- ParticipantData getSelfParticipantById(final String selfId) {
- return mSelfParticipantMap.get(selfId);
- }
-
- /**
- * Returns if a given self id represents the default self.
- */
- boolean isDefaultSelf(final String selfId) {
- if (!OsUtil.isAtLeastL_MR1()) {
- return true;
- }
- final ParticipantData self = getSelfParticipantById(selfId);
- return self == null ? false : self.getSubId() == ParticipantData.DEFAULT_SELF_SUB_ID;
- }
-
- public int getSelfParticipantsCountExcludingDefault(final boolean activeOnly) {
- int count = 0;
- for (final ParticipantData self : mSelfParticipantMap.values()) {
- if (!self.isDefaultSelf() && (!activeOnly || self.isActiveSubscription())) {
- count++;
- }
- }
- return count;
- }
-
- public ParticipantData getDefaultSelfParticipant() {
- for (final ParticipantData self : mSelfParticipantMap.values()) {
- if (self.isDefaultSelf()) {
- return self;
- }
- }
- return null;
- }
-
- boolean isLoaded() {
- return !mSelfParticipantMap.isEmpty();
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/SettingsData.java b/src/com/android/messaging/datamodel/data/SettingsData.java
deleted file mode 100644
index 7474619..0000000
--- a/src/com/android/messaging/datamodel/data/SettingsData.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.data;
-
-import android.app.LoaderManager;
-import android.content.Context;
-import android.content.Loader;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.BoundCursorLoader;
-import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.binding.BindableData;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Services SettingsFragment's data needs for loading active self participants to display
- * the list of active subscriptions.
- */
-public class SettingsData extends BindableData implements
- LoaderManager.LoaderCallbacks<Cursor> {
- public interface SettingsDataListener {
- void onSelfParticipantDataLoaded(SettingsData data);
- }
-
- public static class SettingsItem {
- public static final int TYPE_GENERAL_SETTINGS = 1;
- public static final int TYPE_PER_SUBSCRIPTION_SETTINGS = 2;
-
- private final String mDisplayName;
- private final String mDisplayDetail;
- private final String mActivityTitle;
- private final int mType;
- private final int mSubId;
-
- private SettingsItem(final String displayName, final String displayDetail,
- final String activityTitle, final int type, final int subId) {
- mDisplayName = displayName;
- mDisplayDetail = displayDetail;
- mActivityTitle = activityTitle;
- mType = type;
- mSubId = subId;
- }
-
- public String getDisplayName() {
- return mDisplayName;
- }
-
- public String getDisplayDetail() {
- return mDisplayDetail;
- }
-
- public int getType() {
- return mType;
- }
-
- public int getSubId() {
- return mSubId;
- }
-
- public String getActivityTitle() {
- return mActivityTitle;
- }
-
- public static SettingsItem fromSelfParticipant(final Context context,
- final ParticipantData self) {
- Assert.isTrue(self.isSelf());
- Assert.isTrue(self.isActiveSubscription());
- final String displayDetail = TextUtils.isEmpty(self.getDisplayDestination()) ?
- context.getString(R.string.sim_settings_unknown_number) :
- self.getDisplayDestination();
- final String displayName = context.getString(R.string.sim_specific_settings,
- self.getSubscriptionName());
- return new SettingsItem(displayName, displayDetail, displayName,
- TYPE_PER_SUBSCRIPTION_SETTINGS, self.getSubId());
- }
-
- public static SettingsItem createGeneralSettingsItem(final Context context) {
- return new SettingsItem(context.getString(R.string.general_settings),
- null, context.getString(R.string.general_settings_activity_title),
- TYPE_GENERAL_SETTINGS, -1);
- }
-
- public static SettingsItem createDefaultMmsSettingsItem(final Context context,
- final int subId) {
- return new SettingsItem(context.getString(R.string.advanced_settings),
- null, context.getString(R.string.advanced_settings_activity_title),
- TYPE_PER_SUBSCRIPTION_SETTINGS, subId);
- }
- }
-
- private static final String BINDING_ID = "bindingId";
- private final Context mContext;
- private final SelfParticipantsData mSelfParticipantsData;
- private LoaderManager mLoaderManager;
- private SettingsDataListener mListener;
-
- public SettingsData(final Context context,
- final SettingsDataListener listener) {
- mListener = listener;
- mContext = context;
- mSelfParticipantsData = new SelfParticipantsData();
- }
-
- private static final int SELF_PARTICIPANT_LOADER = 1;
-
- @Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- Assert.equals(SELF_PARTICIPANT_LOADER, id);
- Loader<Cursor> loader = null;
-
- final String bindingId = args.getString(BINDING_ID);
- // Check if data still bound to the requesting ui element
- if (isBound(bindingId)) {
- loader = new BoundCursorLoader(bindingId, mContext,
- MessagingContentProvider.PARTICIPANTS_URI,
- ParticipantData.ParticipantsQuery.PROJECTION,
- ParticipantColumns.SUB_ID + " <> ?",
- new String[] { String.valueOf(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
- null);
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Creating self loader after unbinding");
- }
- return loader;
- }
-
- @Override
- public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- mSelfParticipantsData.bind(data);
- mListener.onSelfParticipantDataLoaded(this);
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Self loader finished after unbinding");
- }
- }
-
- @Override
- public void onLoaderReset(final Loader<Cursor> generic) {
- final BoundCursorLoader loader = (BoundCursorLoader) generic;
-
- // Check if data still bound to the requesting ui element
- if (isBound(loader.getBindingId())) {
- mSelfParticipantsData.bind(null);
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Self loader reset after unbinding");
- }
- }
-
- public void init(final LoaderManager loaderManager,
- final BindingBase<SettingsData> binding) {
- final Bundle args = new Bundle();
- args.putString(BINDING_ID, binding.getBindingId());
- mLoaderManager = loaderManager;
- mLoaderManager.initLoader(SELF_PARTICIPANT_LOADER, args, this);
- }
-
- @Override
- protected void unregisterListeners() {
- mListener = null;
-
- // This could be null if we bind but the caller doesn't init the BindableData
- if (mLoaderManager != null) {
- mLoaderManager.destroyLoader(SELF_PARTICIPANT_LOADER);
- mLoaderManager = null;
- }
- }
-
- public List<SettingsItem> getSettingsItems() {
- final List<ParticipantData> selfs = mSelfParticipantsData.getSelfParticipants(true);
- final List<SettingsItem> settingsItems = new ArrayList<SettingsItem>();
- // First goes the general settings, followed by per-subscription settings.
- settingsItems.add(SettingsItem.createGeneralSettingsItem(mContext));
- // For per-subscription settings, show the actual SIM name with phone number if the
- // platorm is at least L-MR1 and there are multiple active SIMs.
- final int activeSubCountExcludingDefault =
- mSelfParticipantsData.getSelfParticipantsCountExcludingDefault(true);
- if (OsUtil.isAtLeastL_MR1() && activeSubCountExcludingDefault > 0) {
- for (ParticipantData self : selfs) {
- if (!self.isDefaultSelf()) {
- if (activeSubCountExcludingDefault > 1) {
- settingsItems.add(SettingsItem.fromSelfParticipant(mContext, self));
- } else {
- // This is the only active non-default SIM.
- settingsItems.add(SettingsItem.createDefaultMmsSettingsItem(mContext,
- self.getSubId()));
- break;
- }
- }
- }
- } else {
- // Either pre-L-MR1, or there's no active SIM, so show the default MMS settings.
- settingsItems.add(SettingsItem.createDefaultMmsSettingsItem(mContext,
- ParticipantData.DEFAULT_SELF_SUB_ID));
- }
- return settingsItems;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/SubscriptionListData.java b/src/com/android/messaging/datamodel/data/SubscriptionListData.java
deleted file mode 100644
index b5d4e4b..0000000
--- a/src/com/android/messaging/datamodel/data/SubscriptionListData.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.data;
-
-import android.content.Context;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * This is a UI facing data model component that holds a list of
- * {@link SubscriptionListData.SubscriptionListEntry}'s, one for each *active* subscriptions.
- *
- * This is used to:
- * 1) Show a list of SIMs in the SIM Selector
- * 2) Show the currently selected SIM in the compose message view
- * 3) Show SIM indicators on conversation message views
- *
- * It builds on top of SelfParticipantsData and performs additional logic such as determining
- * the set of icons to use for the individual Subs.
- */
-public class SubscriptionListData {
- /**
- * Represents a single sub that backs UI.
- */
- public static class SubscriptionListEntry {
- public final String selfParticipantId;
- public final Uri iconUri;
- public final Uri selectedIconUri;
- public final String displayName;
- public final int displayColor;
- public final String displayDestination;
-
- private SubscriptionListEntry(final String selfParticipantId, final Uri iconUri,
- final Uri selectedIconUri, final String displayName, final int displayColor,
- final String displayDestination) {
- this.selfParticipantId = selfParticipantId;
- this.iconUri = iconUri;
- this.selectedIconUri = selectedIconUri;
- this.displayName = displayName;
- this.displayColor = displayColor;
- this.displayDestination = displayDestination;
- }
-
- static SubscriptionListEntry fromSelfParticipantData(
- final ParticipantData selfParticipantData, final Context context) {
- Assert.isTrue(selfParticipantData.isSelf());
- Assert.isTrue(selfParticipantData.isActiveSubscription());
- final int slotId = selfParticipantData.getDisplaySlotId();
- final String iconIdentifier = String.format(Locale.getDefault(), "%d", slotId);
- final String subscriptionName = selfParticipantData.getSubscriptionName();
- final String displayName = TextUtils.isEmpty(subscriptionName) ?
- context.getString(R.string.sim_slot_identifier, slotId) : subscriptionName;
- return new SubscriptionListEntry(selfParticipantData.getId(),
- AvatarUriUtil.createAvatarUri(selfParticipantData, iconIdentifier,
- false /* selected */, false /* incoming */),
- AvatarUriUtil.createAvatarUri(selfParticipantData, iconIdentifier,
- true /* selected */, false /* incoming */),
- displayName, selfParticipantData.getSubscriptionColor(),
- selfParticipantData.getDisplayDestination());
- }
- }
-
- private final List<SubscriptionListEntry> mEntriesExcludingDefault;
- private SubscriptionListEntry mDefaultEntry;
- private final Context mContext;
-
- public SubscriptionListData(final Context context) {
- mEntriesExcludingDefault = new ArrayList<SubscriptionListEntry>();
- mContext = context;
- }
-
- public void bind(final List<ParticipantData> subs) {
- mEntriesExcludingDefault.clear();
- mDefaultEntry = null;
- for (final ParticipantData sub : subs) {
- final SubscriptionListEntry entry =
- SubscriptionListEntry.fromSelfParticipantData(sub, mContext);
- if (!sub.isDefaultSelf()) {
- mEntriesExcludingDefault.add(entry);
- } else {
- mDefaultEntry = entry;
- }
- }
- }
-
- public List<SubscriptionListEntry> getActiveSubscriptionEntriesExcludingDefault() {
- return mEntriesExcludingDefault;
- }
-
- public SubscriptionListEntry getActiveSubscriptionEntryBySelfId(final String selfId,
- final boolean excludeDefault) {
- if (mDefaultEntry != null && TextUtils.equals(mDefaultEntry.selfParticipantId, selfId)) {
- return excludeDefault ? null : mDefaultEntry;
- }
-
- for (final SubscriptionListEntry entry : mEntriesExcludingDefault) {
- if (TextUtils.equals(entry.selfParticipantId, selfId)) {
- return entry;
- }
- }
- return null;
- }
-
- public boolean hasData() {
- return !mEntriesExcludingDefault.isEmpty() || mDefaultEntry != null;
- }
-}
diff --git a/src/com/android/messaging/datamodel/data/VCardContactItemData.java b/src/com/android/messaging/datamodel/data/VCardContactItemData.java
deleted file mode 100644
index 8abf493..0000000
--- a/src/com/android/messaging/datamodel/data/VCardContactItemData.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.data;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.media.BindableMediaRequest;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-import com.android.messaging.datamodel.media.VCardRequestDescriptor;
-import com.android.messaging.datamodel.media.VCardResource;
-import com.android.messaging.datamodel.media.VCardResourceEntry;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ContactUtil;
-
-import java.util.List;
-
-/**
- * Data class for visualizing and loading data for a VCard contact.
- */
-public class VCardContactItemData extends PersonItemData
- implements MediaResourceLoadListener<VCardResource> {
- private final Context mContext;
- private final Uri mVCardUri;
- private String mDetails;
- private final Binding<BindableMediaRequest<VCardResource>> mBinding =
- BindingBase.createBinding(this);
- private VCardResource mVCardResource;
-
- private static final Uri sDefaultAvatarUri =
- AvatarUriUtil.createAvatarUri(null, null, null, null);
-
- /**
- * Constructor. This parses data from the given MessagePartData describing the vcard
- */
- public VCardContactItemData(final Context context, final MessagePartData messagePartData) {
- this(context, messagePartData.getContentUri());
- Assert.isTrue(messagePartData.isVCard());
- }
-
- /**
- * Constructor. This parses data from the given VCard Uri
- */
- public VCardContactItemData(final Context context, final Uri vCardUri) {
- mContext = context;
- mDetails = mContext.getString(R.string.loading_vcard);
- mVCardUri = vCardUri;
- }
-
- @Override
- public Uri getAvatarUri() {
- if (hasValidVCard()) {
- final List<VCardResourceEntry> vcards = mVCardResource.getVCards();
- Assert.isTrue(vcards.size() > 0);
- if (vcards.size() == 1) {
- return vcards.get(0).getAvatarUri();
- }
- }
- return sDefaultAvatarUri;
- }
-
- @Override
- public String getDisplayName() {
- if (hasValidVCard()) {
- final List<VCardResourceEntry> vcards = mVCardResource.getVCards();
- Assert.isTrue(vcards.size() > 0);
- if (vcards.size() == 1) {
- return vcards.get(0).getDisplayName();
- } else {
- return mContext.getResources().getQuantityString(
- R.plurals.vcard_multiple_display_name, vcards.size(), vcards.size());
- }
- }
- return null;
- }
-
- @Override
- public String getDetails() {
- return mDetails;
- }
-
- @Override
- public Intent getClickIntent() {
- return null;
- }
-
- @Override
- public long getContactId() {
- return ContactUtil.INVALID_CONTACT_ID;
- }
-
- @Override
- public String getLookupKey() {
- return null;
- }
-
- @Override
- public String getNormalizedDestination() {
- return null;
- }
-
- public VCardResource getVCardResource() {
- return hasValidVCard() ? mVCardResource : null;
- }
-
- public Uri getVCardUri() {
- return hasValidVCard() ? mVCardUri : null;
- }
-
- public boolean hasValidVCard() {
- return isBound() && mVCardResource != null;
- }
-
- @Override
- public void bind(final String bindingId) {
- super.bind(bindingId);
-
- // Bind and request the VCard from media resource manager.
- mBinding.bind(new VCardRequestDescriptor(mVCardUri).buildAsyncMediaRequest(mContext, this));
- MediaResourceManager.get().requestMediaResourceAsync(mBinding.getData());
- }
-
- @Override
- public void unbind(final String bindingId) {
- super.unbind(bindingId);
- mBinding.unbind();
- if (mVCardResource != null) {
- mVCardResource.release();
- mVCardResource = null;
- }
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof VCardContactItemData)) {
- return false;
- }
-
- final VCardContactItemData lhs = (VCardContactItemData) o;
- return mVCardUri.equals(lhs.mVCardUri);
- }
-
- @Override
- public void onMediaResourceLoaded(final MediaRequest<VCardResource> request,
- final VCardResource resource, final boolean isCached) {
- Assert.isTrue(mVCardResource == null);
- mBinding.ensureBound();
- mDetails = mContext.getString(R.string.vcard_tap_hint);
- mVCardResource = resource;
- mVCardResource.addRef();
- notifyDataUpdated();
- }
-
- @Override
- public void onMediaResourceLoadError(final MediaRequest<VCardResource> request,
- final Exception exception) {
- mBinding.ensureBound();
- mDetails = mContext.getString(R.string.failed_loading_vcard);
- notifyDataFailed(exception);
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java b/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java
deleted file mode 100644
index 380d93c..0000000
--- a/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-
-import java.util.List;
-
-/**
- * A mix-in style class that wraps around a normal, threading-agnostic MediaRequest object with
- * functionalities offered by {@link BindableMediaRequest} to allow for async processing.
- */
-class AsyncMediaRequestWrapper<T extends RefCountedMediaResource> extends BindableMediaRequest<T> {
-
- /**
- * Create a new async media request wrapper instance given the listener.
- */
- public static <T extends RefCountedMediaResource> AsyncMediaRequestWrapper<T>
- createWith(final MediaRequest<T> wrappedRequest,
- final MediaResourceLoadListener<T> listener) {
- return new AsyncMediaRequestWrapper<T>(listener, wrappedRequest);
- }
-
- private final MediaRequest<T> mWrappedRequest;
-
- private AsyncMediaRequestWrapper(final MediaResourceLoadListener<T> listener,
- final MediaRequest<T> wrappedRequest) {
- super(listener);
- mWrappedRequest = wrappedRequest;
- }
-
- @Override
- public String getKey() {
- return mWrappedRequest.getKey();
- }
-
- @Override
- public MediaCache<T> getMediaCache() {
- return mWrappedRequest.getMediaCache();
- }
-
- @Override
- public int getRequestType() {
- return mWrappedRequest.getRequestType();
- }
-
- @Override
- public T loadMediaBlocking(List<MediaRequest<T>> chainedTask) throws Exception {
- return mWrappedRequest.loadMediaBlocking(chainedTask);
- }
-
- @Override
- public int getCacheId() {
- return mWrappedRequest.getCacheId();
- }
-
- @Override
- public MediaRequestDescriptor<T> getDescriptor() {
- return mWrappedRequest.getDescriptor();
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java b/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java
deleted file mode 100644
index 719b296..0000000
--- a/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.graphics.RectF;
-import android.net.Uri;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class AvatarGroupRequestDescriptor extends CompositeImageRequestDescriptor {
- private static final int MAX_GROUP_SIZE = 4;
-
- public AvatarGroupRequestDescriptor(final Uri uri, final int desiredWidth,
- final int desiredHeight) {
- this(convertToDescriptor(uri, desiredWidth, desiredHeight), desiredWidth, desiredHeight);
- }
-
- public AvatarGroupRequestDescriptor(final List<? extends ImageRequestDescriptor> descriptors,
- final int desiredWidth, final int desiredHeight) {
- super(descriptors, desiredWidth, desiredHeight);
- Assert.isTrue(descriptors.size() <= MAX_GROUP_SIZE);
- }
-
- private static List<? extends ImageRequestDescriptor> convertToDescriptor(final Uri uri,
- final int desiredWidth, final int desiredHeight) {
- final List<String> participantUriStrings = AvatarUriUtil.getGroupParticipantUris(uri);
- final List<AvatarRequestDescriptor> avatarDescriptors =
- new ArrayList<AvatarRequestDescriptor>(participantUriStrings.size());
- for (final String uriString : participantUriStrings) {
- final AvatarRequestDescriptor descriptor = new AvatarRequestDescriptor(
- Uri.parse(uriString), desiredWidth, desiredHeight);
- avatarDescriptors.add(descriptor);
- }
- return avatarDescriptors;
- }
-
- @Override
- public CompositeImageRequest<?> buildBatchImageRequest(final Context context) {
- return new CompositeImageRequest<AvatarGroupRequestDescriptor>(context, this);
- }
-
- @Override
- public List<RectF> getChildRequestTargetRects() {
- return Arrays.asList(generateDestRectArray());
- }
-
- /**
- * Generates an array of {@link RectF} which represents where each of the individual avatar
- * should be located in the final group avatar image. The location of each avatar depends on
- * the size of the group and the size of the overall group avatar size.
- */
- private RectF[] generateDestRectArray() {
- final int groupSize = mDescriptors.size();
- final float width = desiredWidth;
- final float height = desiredHeight;
- final float halfWidth = width / 2F;
- final float halfHeight = height / 2F;
- final RectF[] destArray = new RectF[groupSize];
- switch (groupSize) {
- case 2:
- /**
- * +-------+
- * | 0 | |
- * +-------+
- * | | 1 |
- * +-------+
- *
- * We want two circles which touches in the center. To get this we know that the
- * diagonal of the overall group avatar is squareRoot(2) * w We also know that the
- * two circles touches the at the center of the overall group avatar and the
- * distance from the center of the circle to the corner of the group avatar is
- * radius * squareRoot(2). Therefore, the following emerges.
- *
- * w * squareRoot(2) = 2 (radius + radius * squareRoot(2))
- * Solving for radius we get:
- * d = 2 * radius = ( squareRoot(2) / (squareRoot(2) + 1)) * w
- * d = (2 - squareRoot(2)) * w
- */
- final float diameter = (float) ((2 - Math.sqrt(2)) * width);
- destArray[0] = new RectF(0, 0, diameter, diameter);
- destArray[1] = new RectF(width - diameter, height - diameter, width, height);
- break;
- case 3:
- /**
- * +-------+
- * | | 0 | |
- * +-------+
- * | 1 | 2 |
- * +-------+
- * i0
- * |\
- * a | \ c
- * --- i2
- * b
- *
- * a = radius * squareRoot(3) due to the triangle being a 30-60-90 right triangle.
- * b = radius of circle
- * c = 2 * radius of circle
- *
- * All three of the images are circles and therefore image zero will not touch
- * image one or image two. Move image zero down so it touches image one and image
- * two. This can be done by keeping image zero in the center and moving it down
- * slightly. The amount to move down can be calculated by solving a right triangle.
- * We know that the center x of image two to the center x of image zero is the
- * radius of the circle, this is the length of edge b. Also we know that the
- * distance from image zero to image two's center is 2 * radius, edge c. From this
- * we know that the distance from center y of image two to center y of image one,
- * edge a, is equal to radius * squareRoot(3) due to this triangle being a 30-60-90
- * right triangle.
- */
- final float quarterWidth = width / 4F;
- final float threeQuarterWidth = 3 * quarterWidth;
- final float radius = height / 4F;
- final float imageTwoCenterY = height - radius;
- final float lengthOfEdgeA = (float) (radius * Math.sqrt(3));
- final float imageZeroCenterY = imageTwoCenterY - lengthOfEdgeA;
- final float imageZeroTop = imageZeroCenterY - radius;
- final float imageZeroBottom = imageZeroCenterY + radius;
- destArray[0] = new RectF(
- quarterWidth, imageZeroTop, threeQuarterWidth, imageZeroBottom);
- destArray[1] = new RectF(0, halfHeight, halfWidth, height);
- destArray[2] = new RectF(halfWidth, halfHeight, width, height);
- break;
- default:
- /**
- * +-------+
- * | 0 | 1 |
- * +-------+
- * | 2 | 3 |
- * +-------+
- */
- destArray[0] = new RectF(0, 0, halfWidth, halfHeight);
- destArray[1] = new RectF(halfWidth, 0, width, halfHeight);
- destArray[2] = new RectF(0, halfHeight, halfWidth, height);
- destArray[3] = new RectF(halfWidth, halfHeight, width, height);
- break;
- }
- return destArray;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/AvatarRequest.java b/src/com/android/messaging/datamodel/media/AvatarRequest.java
deleted file mode 100644
index 22d5ccc..0000000
--- a/src/com/android/messaging/datamodel/media/AvatarRequest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.media.ExifInterface;
-import android.net.Uri;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UriUtil;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-public class AvatarRequest extends UriImageRequest<AvatarRequestDescriptor> {
- private static Bitmap sDefaultPersonBitmap;
- private static Bitmap sDefaultPersonBitmapLarge;
-
- public AvatarRequest(final Context context,
- final AvatarRequestDescriptor descriptor) {
- super(context, descriptor);
- }
-
- @Override
- protected InputStream getInputStreamForResource() throws FileNotFoundException {
- if (UriUtil.isLocalResourceUri(mDescriptor.uri)) {
- return super.getInputStreamForResource();
- } else {
- final Uri primaryUri = AvatarUriUtil.getPrimaryUri(mDescriptor.uri);
- Assert.isTrue(UriUtil.isLocalResourceUri(primaryUri));
- return mContext.getContentResolver().openInputStream(primaryUri);
- }
- }
-
- /**
- * We can load multiple types of images for avatars depending on the uri. The uri should be
- * built by {@link com.android.messaging.util.AvatarUriUtil} which will decide on
- * what uri to build based on the available profile photo and name. Here we will check if the
- * image is a local resource (ie profile photo uri), if the resource isn't a local one we will
- * generate a tile with the first letter of the name.
- */
- @Override
- protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTasks)
- throws IOException {
- Assert.isNotMainThread();
- String avatarType = AvatarUriUtil.getAvatarType(mDescriptor.uri);
- Bitmap bitmap = null;
- int orientation = ExifInterface.ORIENTATION_NORMAL;
- final boolean isLocalResourceUri = UriUtil.isLocalResourceUri(mDescriptor.uri) ||
- AvatarUriUtil.TYPE_LOCAL_RESOURCE_URI.equals(avatarType);
- if (isLocalResourceUri) {
- try {
- ImageResource imageResource = super.loadMediaInternal(chainedTasks);
- bitmap = imageResource.getBitmap();
- orientation = imageResource.mOrientation;
- } catch (Exception ex) {
- // If we encountered any exceptions trying to load the local avatar resource,
- // fall back to generated avatar.
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "AvatarRequest: failed to load local avatar " +
- "resource, switching to fallback rendering", ex);
- }
- }
-
- final int width = mDescriptor.desiredWidth;
- final int height = mDescriptor.desiredHeight;
- // Check to see if we already got the bitmap. If not get a fallback avatar
- if (bitmap == null) {
- Uri generatedUri = mDescriptor.uri;
- if (isLocalResourceUri) {
- // If we are here, we just failed to load the local resource. Use the fallback Uri
- // if possible.
- generatedUri = AvatarUriUtil.getFallbackUri(mDescriptor.uri);
- if (generatedUri == null) {
- // No fallback Uri was provided, use the default avatar.
- generatedUri = AvatarUriUtil.DEFAULT_BACKGROUND_AVATAR;
- }
- }
-
- avatarType = AvatarUriUtil.getAvatarType(generatedUri);
- if (AvatarUriUtil.TYPE_LETTER_TILE_URI.equals(avatarType)) {
- final String name = AvatarUriUtil.getName(generatedUri);
- bitmap = renderLetterTile(name, width, height);
- } else {
- bitmap = renderDefaultAvatar(width, height);
- }
- }
- return new DecodedImageResource(getKey(), bitmap, orientation);
- }
-
- private Bitmap renderDefaultAvatar(final int width, final int height) {
- final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height,
- getBackgroundColor());
- final Canvas canvas = new Canvas(bitmap);
-
- if (sDefaultPersonBitmap == null) {
- final BitmapDrawable defaultPerson = (BitmapDrawable) mContext.getResources()
- .getDrawable(R.drawable.ic_person_light);
- sDefaultPersonBitmap = defaultPerson.getBitmap();
- }
- if (sDefaultPersonBitmapLarge == null) {
- final BitmapDrawable largeDefaultPerson = (BitmapDrawable) mContext.getResources()
- .getDrawable(R.drawable.ic_person_light_large);
- sDefaultPersonBitmapLarge = largeDefaultPerson.getBitmap();
- }
-
- Bitmap defaultPerson = null;
- if (mDescriptor.isWearBackground) {
- final BitmapDrawable wearDefaultPerson = (BitmapDrawable) mContext.getResources()
- .getDrawable(R.drawable.ic_person_wear);
- defaultPerson = wearDefaultPerson.getBitmap();
- } else {
- final boolean isLargeDefault = (width > sDefaultPersonBitmap.getWidth()) ||
- (height > sDefaultPersonBitmap.getHeight());
- defaultPerson =
- isLargeDefault ? sDefaultPersonBitmapLarge : sDefaultPersonBitmap;
- }
-
- final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- final Matrix matrix = new Matrix();
- final RectF source = new RectF(0, 0, defaultPerson.getWidth(), defaultPerson.getHeight());
- final RectF dest = new RectF(0, 0, width, height);
- matrix.setRectToRect(source, dest, Matrix.ScaleToFit.FILL);
-
- canvas.drawBitmap(defaultPerson, matrix, paint);
-
- return bitmap;
- }
-
- private Bitmap renderLetterTile(final String name, final int width, final int height) {
- final float halfWidth = width / 2;
- final float halfHeight = height / 2;
- final int minOfWidthAndHeight = Math.min(width, height);
- final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height,
- getBackgroundColor());
- final Resources resources = mContext.getResources();
- final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- paint.setTypeface(Typeface.create("sans-serif-thin", Typeface.NORMAL));
- paint.setColor(resources.getColor(R.color.letter_tile_font_color));
- final float letterToTileRatio = resources.getFraction(R.dimen.letter_to_tile_ratio, 1, 1);
- paint.setTextSize(letterToTileRatio * minOfWidthAndHeight);
-
- final String firstCharString = name.substring(0, 1).toUpperCase();
- final Rect textBound = new Rect();
- paint.getTextBounds(firstCharString, 0, 1, textBound);
-
- final Canvas canvas = new Canvas(bitmap);
- final float xOffset = halfWidth - textBound.centerX();
- final float yOffset = halfHeight - textBound.centerY();
- canvas.drawText(firstCharString, xOffset, yOffset, paint);
-
- return bitmap;
- }
-
- private int getBackgroundColor() {
- return mContext.getResources().getColor(R.color.primary_color);
- }
-
- @Override
- public int getCacheId() {
- return BugleMediaCacheManager.AVATAR_IMAGE_CACHE;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java b/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java
deleted file mode 100644
index 9afa9ad..0000000
--- a/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.net.Uri;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.UriUtil;
-
-public class AvatarRequestDescriptor extends UriImageRequestDescriptor {
- final boolean isWearBackground;
-
- public AvatarRequestDescriptor(final Uri uri, final int desiredWidth,
- final int desiredHeight) {
- this(uri, desiredWidth, desiredHeight, true /* cropToCircle */);
- }
-
- public AvatarRequestDescriptor(final Uri uri, final int desiredWidth,
- final int desiredHeight, final boolean cropToCircle) {
- this(uri, desiredWidth, desiredHeight, cropToCircle, false /* isWearBackground */);
- }
-
- public AvatarRequestDescriptor(final Uri uri, final int desiredWidth,
- final int desiredHeight, boolean cropToCircle, boolean isWearBackground) {
- super(uri, desiredWidth, desiredHeight, false /* allowCompression */, true /* isStatic */,
- cropToCircle,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- Assert.isTrue(uri == null || UriUtil.isLocalResourceUri(uri) ||
- AvatarUriUtil.isAvatarUri(uri));
- this.isWearBackground = isWearBackground;
- }
-
- @Override
- public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
- final String avatarType = uri == null ? null : AvatarUriUtil.getAvatarType(uri);
- if (AvatarUriUtil.TYPE_SIM_SELECTOR_URI.equals(avatarType)) {
- return new SimSelectorAvatarRequest(context, this);
- } else {
- return new AvatarRequest(context, this);
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/BindableMediaRequest.java b/src/com/android/messaging/datamodel/media/BindableMediaRequest.java
deleted file mode 100644
index 36521d5..0000000
--- a/src/com/android/messaging/datamodel/media/BindableMediaRequest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import com.android.messaging.datamodel.binding.BindableOnceData;
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-
-/**
- * The {@link MediaRequest} interface is threading-model-blind, allowing the implementations to
- * be processed synchronously or asynchronously.
- * This is a {@link MediaRequest} implementation that includes functionalities such as binding and
- * event callbacks for multi-threaded media request processing.
- */
-public abstract class BindableMediaRequest<T extends RefCountedMediaResource>
- extends BindableOnceData
- implements MediaRequest<T>, MediaResourceLoadListener<T> {
- private MediaResourceLoadListener<T> mListener;
-
- public BindableMediaRequest(final MediaResourceLoadListener<T> listener) {
- mListener = listener;
- }
-
- /**
- * Delegates the media resource callback to the listener. Performs binding check to ensure
- * the listener is still bound to this request.
- */
- @Override
- public void onMediaResourceLoaded(final MediaRequest<T> request, final T resource,
- final boolean cached) {
- if (isBound() && mListener != null) {
- mListener.onMediaResourceLoaded(request, resource, cached);
- }
- }
-
- /**
- * Delegates the media resource callback to the listener. Performs binding check to ensure
- * the listener is still bound to this request.
- */
- @Override
- public void onMediaResourceLoadError(final MediaRequest<T> request, final Exception exception) {
- if (isBound() && mListener != null) {
- mListener.onMediaResourceLoadError(request, exception);
- }
- }
-
- @Override
- protected void unregisterListeners() {
- mListener = null;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java b/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java
deleted file mode 100644
index c41ba60..0000000
--- a/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import com.android.messaging.util.Assert;
-
-/**
- * An implementation of {@link MediaCacheManager} that creates caches specific to Bugle's needs.
- *
- * To create a new type of cache, add to the list of cache ids and create a new MediaCache<>
- * for your cache id / media resource type in createMediaCacheById().
- */
-public class BugleMediaCacheManager extends MediaCacheManager {
- // List of available cache ids.
- public static final int DEFAULT_IMAGE_CACHE = 1;
- public static final int AVATAR_IMAGE_CACHE = 2;
- public static final int VCARD_CACHE = 3;
-
- // VCard cache size - we compute the size by count, not by bytes.
- private static final int VCARD_CACHE_SIZE = 5;
- private static final int SHARED_IMAGE_CACHE_SIZE = 1024 * 10; // 10MB
-
- @Override
- protected MediaCache<?> createMediaCacheById(final int id) {
- switch (id) {
- case DEFAULT_IMAGE_CACHE:
- return new PoolableImageCache(SHARED_IMAGE_CACHE_SIZE, id, "DefaultImageCache");
-
- case AVATAR_IMAGE_CACHE:
- return new PoolableImageCache(id, "AvatarImageCache");
-
- case VCARD_CACHE:
- return new MediaCache<VCardResource>(VCARD_CACHE_SIZE, id, "VCardCache");
-
- default:
- Assert.fail("BugleMediaCacheManager: unsupported cache id " + id);
- break;
- }
- return null;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/CompositeImageRequest.java b/src/com/android/messaging/datamodel/media/CompositeImageRequest.java
deleted file mode 100644
index 66f1bff..0000000
--- a/src/com/android/messaging/datamodel/media/CompositeImageRequest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.media.ExifInterface;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ImageUtils;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * Requests a composite image resource. The composite image resource is constructed by first
- * sequentially requesting a number of sub image resources specified by
- * {@link CompositeImageRequestDescriptor#getChildRequestDescriptors()}. After this, the
- * individual sub images are composed into the final image onto their respective target rects
- * returned by {@link CompositeImageRequestDescriptor#getChildRequestTargetRects()}.
- */
-public class CompositeImageRequest<D extends CompositeImageRequestDescriptor>
- extends ImageRequest<D> {
- private final Bitmap mBitmap;
- private final Canvas mCanvas;
- private final Paint mPaint;
-
- public CompositeImageRequest(final Context context, final D descriptor) {
- super(context, descriptor);
- mBitmap = getBitmapPool().createOrReuseBitmap(
- mDescriptor.desiredWidth, mDescriptor.desiredHeight);
- mCanvas = new Canvas(mBitmap);
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- }
-
- @Override
- protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTask) {
- final List<? extends ImageRequestDescriptor> descriptors =
- mDescriptor.getChildRequestDescriptors();
- final List<RectF> targetRects = mDescriptor.getChildRequestTargetRects();
- Assert.equals(descriptors.size(), targetRects.size());
- Assert.isTrue(descriptors.size() > 1);
-
- for (int i = 0; i < descriptors.size(); i++) {
- final MediaRequest<ImageResource> request =
- descriptors.get(i).buildSyncMediaRequest(mContext);
- // Synchronously request the child image.
- final ImageResource resource =
- MediaResourceManager.get().requestMediaResourceSync(request);
- if (resource != null) {
- try {
- final RectF avatarDestOnGroup = targetRects.get(i);
-
- // Draw the bitmap into a smaller size with a circle mask.
- final Bitmap resourceBitmap = resource.getBitmap();
- final RectF resourceRect = new RectF(
- 0, 0, resourceBitmap.getWidth(), resourceBitmap.getHeight());
- final Bitmap smallCircleBitmap = getBitmapPool().createOrReuseBitmap(
- Math.round(avatarDestOnGroup.width()),
- Math.round(avatarDestOnGroup.height()));
- final RectF smallCircleRect = new RectF(
- 0, 0, smallCircleBitmap.getWidth(), smallCircleBitmap.getHeight());
- final Canvas smallCircleCanvas = new Canvas(smallCircleBitmap);
- ImageUtils.drawBitmapWithCircleOnCanvas(resource.getBitmap(), smallCircleCanvas,
- resourceRect, smallCircleRect, null /* bitmapPaint */,
- false /* fillBackground */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- final Matrix matrix = new Matrix();
- matrix.setRectToRect(smallCircleRect, avatarDestOnGroup,
- Matrix.ScaleToFit.FILL);
- mCanvas.drawBitmap(smallCircleBitmap, matrix, mPaint);
- } finally {
- resource.release();
- }
- }
- }
-
- return new DecodedImageResource(getKey(), mBitmap, ExifInterface.ORIENTATION_NORMAL);
- }
-
- @Override
- public int getCacheId() {
- return BugleMediaCacheManager.AVATAR_IMAGE_CACHE;
- }
-
- @Override
- protected InputStream getInputStreamForResource() throws FileNotFoundException {
- throw new IllegalStateException("Composite image request doesn't support input stream!");
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java
deleted file mode 100644
index 071130e..0000000
--- a/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.graphics.RectF;
-
-import com.google.common.base.Joiner;
-
-import java.util.List;
-
-public abstract class CompositeImageRequestDescriptor extends ImageRequestDescriptor {
- protected final List<? extends ImageRequestDescriptor> mDescriptors;
- private final String mKey;
-
- public CompositeImageRequestDescriptor(final List<? extends ImageRequestDescriptor> descriptors,
- final int desiredWidth, final int desiredHeight) {
- super(desiredWidth, desiredHeight);
- mDescriptors = descriptors;
-
- final String[] keyParts = new String[descriptors.size()];
- for (int i = 0; i < descriptors.size(); i++) {
- keyParts[i] = descriptors.get(i).getKey();
- }
- mKey = Joiner.on(",").skipNulls().join(keyParts);
- }
-
- /**
- * Gets a key that uniquely identify all the underlying image resource to be loaded (e.g. Uri or
- * file path).
- */
- @Override
- public String getKey() {
- return mKey;
- }
-
- public List<? extends ImageRequestDescriptor> getChildRequestDescriptors(){
- return mDescriptors;
- }
-
- public abstract List<RectF> getChildRequestTargetRects();
- public abstract CompositeImageRequest<?> buildBatchImageRequest(final Context context);
-
- @Override
- public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
- return buildBatchImageRequest(context);
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/CustomVCardEntry.java b/src/com/android/messaging/datamodel/media/CustomVCardEntry.java
deleted file mode 100644
index aee9fdc..0000000
--- a/src/com/android/messaging/datamodel/media/CustomVCardEntry.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.accounts.Account;
-import android.support.v4.util.ArrayMap;
-
-import com.android.vcard.VCardEntry;
-import com.android.vcard.VCardProperty;
-
-import java.util.Map;
-
-/**
- * Class which extends VCardEntry to add support for unknown properties. Currently there is a TODO
- * to add this in the VCardEntry code, but we have to extend it to add the needed support
- */
-public class CustomVCardEntry extends VCardEntry {
- // List of properties keyed by their name for easy lookup
- private final Map<String, VCardProperty> mAllProperties;
-
- public CustomVCardEntry(int vCardType, Account account) {
- super(vCardType, account);
- mAllProperties = new ArrayMap<String, VCardProperty>();
- }
-
- @Override
- public void addProperty(VCardProperty property) {
- super.addProperty(property);
- mAllProperties.put(property.getName(), property);
- }
-
- public VCardProperty getProperty(String name) {
- return mAllProperties.get(name);
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java b/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java
deleted file mode 100644
index 06b10a3..0000000
--- a/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.accounts.Account;
-import com.android.vcard.VCardConfig;
-import com.android.vcard.VCardInterpreter;
-import com.android.vcard.VCardProperty;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class CustomVCardEntryConstructor implements VCardInterpreter {
-
- public interface EntryHandler {
- /**
- * Called when the parsing started.
- */
- public void onStart();
-
- /**
- * The method called when one vCard entry is created. Children come before their parent in
- * nested vCard files.
- *
- * e.g.
- * In the following vCard, the entry for "entry2" comes before one for "entry1".
- * <code>
- * BEGIN:VCARD
- * N:entry1
- * BEGIN:VCARD
- * N:entry2
- * END:VCARD
- * END:VCARD
- * </code>
- */
- public void onEntryCreated(final CustomVCardEntry entry);
-
- /**
- * Called when the parsing ended.
- * Able to be use this method for showing performance log, etc.
- */
- public void onEnd();
- }
-
- /**
- * Represents current stack of VCardEntry. Used to support nested vCard (vCard 2.1).
- */
- private final List<CustomVCardEntry> mEntryStack = new ArrayList<CustomVCardEntry>();
- private CustomVCardEntry mCurrentEntry;
-
- private final int mVCardType;
- private final Account mAccount;
-
- private final List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
-
- public CustomVCardEntryConstructor() {
- this(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
- }
-
- public CustomVCardEntryConstructor(final int vcardType) {
- this(vcardType, null);
- }
-
- public CustomVCardEntryConstructor(final int vcardType, final Account account) {
- mVCardType = vcardType;
- mAccount = account;
- }
-
- public void addEntryHandler(EntryHandler entryHandler) {
- mEntryHandlers.add(entryHandler);
- }
-
- @Override
- public void onVCardStarted() {
- for (EntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onStart();
- }
- }
-
- @Override
- public void onVCardEnded() {
- for (EntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onEnd();
- }
- }
-
- public void clear() {
- mCurrentEntry = null;
- mEntryStack.clear();
- }
-
- @Override
- public void onEntryStarted() {
- mCurrentEntry = new CustomVCardEntry(mVCardType, mAccount);
- mEntryStack.add(mCurrentEntry);
- }
-
- @Override
- public void onEntryEnded() {
- mCurrentEntry.consolidateFields();
- for (EntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onEntryCreated(mCurrentEntry);
- }
-
- final int size = mEntryStack.size();
- if (size > 1) {
- CustomVCardEntry parent = mEntryStack.get(size - 2);
- parent.addChild(mCurrentEntry);
- mCurrentEntry = parent;
- } else {
- mCurrentEntry = null;
- }
- mEntryStack.remove(size - 1);
- }
-
- @Override
- public void onPropertyCreated(VCardProperty property) {
- mCurrentEntry.addProperty(property);
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/DecodedImageResource.java b/src/com/android/messaging/datamodel/media/DecodedImageResource.java
deleted file mode 100644
index 3627ba4..0000000
--- a/src/com/android/messaging/datamodel/media/DecodedImageResource.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-
-import com.android.messaging.ui.OrientedBitmapDrawable;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-
-import java.util.List;
-
-
-/**
- * Container class for holding a bitmap resource used by the MediaResourceManager. This resource
- * can both be cached (albeit not very storage-efficiently) and directly used by the UI.
- */
-public class DecodedImageResource extends ImageResource {
- private static final int BITMAP_QUALITY = 100;
- private static final int COMPRESS_QUALITY = 50;
-
- private Bitmap mBitmap;
- private final int mOrientation;
- private boolean mCacheable = true;
-
- public DecodedImageResource(final String key, final Bitmap bitmap, int orientation) {
- super(key, orientation);
- mBitmap = bitmap;
- mOrientation = orientation;
- }
-
- /**
- * Gets the contained bitmap.
- */
- @Override
- public Bitmap getBitmap() {
- acquireLock();
- try {
- return mBitmap;
- } finally {
- releaseLock();
- }
- }
-
- /**
- * Attempt to reuse the bitmap in the image resource and repurpose it for something else.
- * After this, the image resource will relinquish ownership on the bitmap resource so that
- * it doesn't try to recycle it when getting closed.
- */
- @Override
- public Bitmap reuseBitmap() {
- acquireLock();
- try {
- assertSingularRefCount();
- final Bitmap retBitmap = mBitmap;
- mBitmap = null;
- return retBitmap;
- } finally {
- releaseLock();
- }
- }
-
- @Override
- public boolean supportsBitmapReuse() {
- return true;
- }
-
- @Override
- public byte[] getBytes() {
- acquireLock();
- try {
- return ImageUtils.bitmapToBytes(mBitmap, BITMAP_QUALITY);
- } catch (final Exception e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error trying to get the bitmap bytes " + e);
- } finally {
- releaseLock();
- }
- return null;
- }
-
- /**
- * Gets the orientation of the image as one of the ExifInterface.ORIENTATION_* constants
- */
- @Override
- public int getOrientation() {
- return mOrientation;
- }
-
- @Override
- public int getMediaSize() {
- acquireLock();
- try {
- Assert.notNull(mBitmap);
- if (OsUtil.isAtLeastKLP()) {
- return mBitmap.getAllocationByteCount();
- } else {
- return mBitmap.getRowBytes() * mBitmap.getHeight();
- }
- } finally {
- releaseLock();
- }
- }
-
- @Override
- protected void close() {
- acquireLock();
- try {
- if (mBitmap != null) {
- mBitmap.recycle();
- mBitmap = null;
- }
- } finally {
- releaseLock();
- }
- }
-
- @Override
- public Drawable getDrawable(Resources resources) {
- acquireLock();
- try {
- Assert.notNull(mBitmap);
- return OrientedBitmapDrawable.create(getOrientation(), resources, mBitmap);
- } finally {
- releaseLock();
- }
- }
-
- @Override
- boolean isCacheable() {
- return mCacheable;
- }
-
- public void setCacheable(final boolean cacheable) {
- mCacheable = cacheable;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- MediaRequest<? extends RefCountedMediaResource> getMediaEncodingRequest(
- final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
- Assert.isFalse(isEncoded());
- if (getBitmap().hasAlpha()) {
- // We can't compress images with alpha, as JPEG encoding doesn't support this.
- return null;
- }
- return new EncodeImageRequest((MediaRequest<ImageResource>) originalRequest);
- }
-
- /**
- * A MediaRequest that encodes the contained image resource.
- */
- private class EncodeImageRequest implements MediaRequest<ImageResource> {
- private final MediaRequest<ImageResource> mOriginalImageRequest;
-
- public EncodeImageRequest(MediaRequest<ImageResource> originalImageRequest) {
- mOriginalImageRequest = originalImageRequest;
- // Hold a ref onto the encoded resource before the request finishes.
- DecodedImageResource.this.addRef();
- }
-
- @Override
- public String getKey() {
- return DecodedImageResource.this.getKey();
- }
-
- @Override
- @DoesNotRunOnMainThread
- public ImageResource loadMediaBlocking(List<MediaRequest<ImageResource>> chainedRequests)
- throws Exception {
- Assert.isNotMainThread();
- acquireLock();
- Bitmap scaledBitmap = null;
- try {
- Bitmap bitmap = getBitmap();
- Assert.isFalse(bitmap.hasAlpha());
- final int bitmapWidth = bitmap.getWidth();
- final int bitmapHeight = bitmap.getHeight();
- // The original bitmap was loaded using sub-sampling which was fast in terms of
- // loading speed, but not optimized for caching, encoding and rendering (since
- // bitmap resizing to fit the UI image views happens on the UI thread and should
- // be avoided if possible). Therefore, try to resize the bitmap to the exact desired
- // size before compressing it.
- if (bitmapWidth > 0 && bitmapHeight > 0 &&
- mOriginalImageRequest instanceof ImageRequest<?>) {
- final ImageRequestDescriptor descriptor =
- ((ImageRequest<?>) mOriginalImageRequest).getDescriptor();
- final float targetScale = Math.max(
- (float) descriptor.desiredWidth / bitmapWidth,
- (float) descriptor.desiredHeight / bitmapHeight);
- final int targetWidth = (int) (bitmapWidth * targetScale);
- final int targetHeight = (int) (bitmapHeight * targetScale);
- // Only try to scale down the image to the desired size.
- if (targetScale < 1.0f && targetWidth > 0 && targetHeight > 0 &&
- targetWidth != bitmapWidth && targetHeight != bitmapHeight) {
- scaledBitmap = bitmap =
- Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, false);
- }
- }
- byte[] encodedBytes = ImageUtils.bitmapToBytes(bitmap, COMPRESS_QUALITY);
- return new EncodedImageResource(getKey(), encodedBytes, getOrientation());
- } catch (Exception ex) {
- // Something went wrong during bitmap compression, fall back to just using the
- // original bitmap.
- LogUtil.e(LogUtil.BUGLE_IMAGE_TAG, "Error compressing bitmap", ex);
- return DecodedImageResource.this;
- } finally {
- if (scaledBitmap != null && scaledBitmap != getBitmap()) {
- scaledBitmap.recycle();
- scaledBitmap = null;
- }
- releaseLock();
- release();
- }
- }
-
- @Override
- public MediaCache<ImageResource> getMediaCache() {
- return mOriginalImageRequest.getMediaCache();
- }
-
- @Override
- public int getCacheId() {
- return mOriginalImageRequest.getCacheId();
- }
-
- @Override
- public int getRequestType() {
- return REQUEST_ENCODE_MEDIA;
- }
-
- @Override
- public MediaRequestDescriptor<ImageResource> getDescriptor() {
- return mOriginalImageRequest.getDescriptor();
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/EncodedImageResource.java b/src/com/android/messaging/datamodel/media/EncodedImageResource.java
deleted file mode 100644
index 0bc94e5..0000000
--- a/src/com/android/messaging/datamodel/media/EncodedImageResource.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Drawable;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A cache-facing image resource that's much more compact than the raw Bitmap objects stored in
- * {@link com.android.messaging.datamodel.media.DecodedImageResource}.
- *
- * This resource is created from a regular Bitmap-based ImageResource before being pushed to
- * {@link com.android.messaging.datamodel.media.MediaCache}, if the image request
- * allows for resource encoding/compression.
- *
- * During resource retrieval on cache hit,
- * {@link #getMediaDecodingRequest(MediaRequest)} is invoked to create a async
- * decode task, which decodes the compressed byte array back to a regular image resource to
- * be consumed by the UI.
- */
-public class EncodedImageResource extends ImageResource {
- private final byte[] mImageBytes;
-
- public EncodedImageResource(String key, byte[] imageBytes, int orientation) {
- super(key, orientation);
- mImageBytes = imageBytes;
- }
-
- @Override
- @DoesNotRunOnMainThread
- public Bitmap getBitmap() {
- acquireLock();
- try {
- // This should only be called during the decode request.
- Assert.isNotMainThread();
- return BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length);
- } finally {
- releaseLock();
- }
- }
-
- @Override
- public byte[] getBytes() {
- acquireLock();
- try {
- return Arrays.copyOf(mImageBytes, mImageBytes.length);
- } finally {
- releaseLock();
- }
- }
-
- @Override
- public Bitmap reuseBitmap() {
- return null;
- }
-
- @Override
- public boolean supportsBitmapReuse() {
- return false;
- }
-
- @Override
- public int getMediaSize() {
- return mImageBytes.length;
- }
-
- @Override
- protected void close() {
- }
-
- @Override
- public Drawable getDrawable(Resources resources) {
- return null;
- }
-
- @Override
- boolean isEncoded() {
- return true;
- }
-
- @Override
- MediaRequest<? extends RefCountedMediaResource> getMediaDecodingRequest(
- final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
- Assert.isTrue(isEncoded());
- return new DecodeImageRequest();
- }
-
- /**
- * A MediaRequest that decodes the encoded image resource. This class is chained to the
- * original media request that requested the image, so it inherits the listener and
- * properties such as binding.
- */
- private class DecodeImageRequest implements MediaRequest<ImageResource> {
- public DecodeImageRequest() {
- // Hold a ref onto the encoded resource before the request finishes.
- addRef();
- }
-
- @Override
- public String getKey() {
- return EncodedImageResource.this.getKey();
- }
-
- @Override
- @DoesNotRunOnMainThread
- public ImageResource loadMediaBlocking(List<MediaRequest<ImageResource>> chainedTask)
- throws Exception {
- Assert.isNotMainThread();
- acquireLock();
- try {
- final Bitmap decodedBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0,
- mImageBytes.length);
- return new DecodedImageResource(getKey(), decodedBitmap, getOrientation());
- } finally {
- releaseLock();
- release();
- }
- }
-
- @Override
- public MediaCache<ImageResource> getMediaCache() {
- // Decoded resource is non-cachable, it's for UI consumption only (for now at least)
- return null;
- }
-
- @Override
- public int getCacheId() {
- return 0;
- }
-
- @Override
- public int getRequestType() {
- return REQUEST_DECODE_MEDIA;
- }
-
- @Override
- public MediaRequestDescriptor<ImageResource> getDescriptor() {
- return null;
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/FileImageRequest.java b/src/com/android/messaging/datamodel/media/FileImageRequest.java
deleted file mode 100644
index 31c053a..0000000
--- a/src/com/android/messaging/datamodel/media/FileImageRequest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.ExifInterface;
-
-import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.LogUtil;
-
-import java.io.IOException;
-
-/**
- * Serves file system based image requests. Since file paths can be expressed in Uri form, this
- * extends regular UriImageRequest but performs additional optimizations such as loading thumbnails
- * directly from Exif information.
- */
-public class FileImageRequest extends UriImageRequest {
- private final String mPath;
- private final boolean mCanUseThumbnail;
-
- public FileImageRequest(final Context context,
- final FileImageRequestDescriptor descriptor) {
- super(context, descriptor);
- mPath = descriptor.path;
- mCanUseThumbnail = descriptor.canUseThumbnail;
- }
-
- @Override
- protected Bitmap loadBitmapInternal()
- throws IOException {
- // Before using the FileInputStream, check if the Exif has a thumbnail that we can use.
- if (mCanUseThumbnail) {
- byte[] thumbnail = null;
- try {
- final ExifInterface exif = new ExifInterface(mPath);
- if (exif.hasThumbnail()) {
- thumbnail = exif.getThumbnail();
- }
- } catch (final IOException e) {
- // Nothing to do
- }
-
- if (thumbnail != null) {
- final BitmapFactory.Options options = PoolableImageCache.getBitmapOptionsForPool(
- false /* scaled */, 0 /* inputDensity */, 0 /* targetDensity */);
- // First, check dimensions of the bitmap.
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length, options);
-
- // Calculate inSampleSize
- options.inSampleSize = ImageUtils.get().calculateInSampleSize(options,
- mDescriptor.desiredWidth, mDescriptor.desiredHeight);
-
- options.inJustDecodeBounds = false;
-
- // Actually decode the bitmap, optionally using the bitmap pool.
- try {
- // Get the orientation. We should be able to get the orientation from
- // the thumbnail itself but at least on some phones, the thumbnail
- // doesn't have an orientation tag. So use the outer image's orientation
- // tag and hope for the best.
- mOrientation = ImageUtils.getOrientation(getInputStreamForResource());
- if (com.android.messaging.util.exif.ExifInterface.
- getOrientationParams(mOrientation).invertDimensions) {
- mDescriptor.updateSourceDimensions(options.outHeight, options.outWidth);
- } else {
- mDescriptor.updateSourceDimensions(options.outWidth, options.outHeight);
- }
- final ReusableImageResourcePool bitmapPool = getBitmapPool();
- if (bitmapPool == null) {
- return BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length,
- options);
- } else {
- final int sampledWidth = options.outWidth / options.inSampleSize;
- final int sampledHeight = options.outHeight / options.inSampleSize;
- return bitmapPool.decodeByteArray(thumbnail, options, sampledWidth,
- sampledHeight);
- }
- } catch (IOException ex) {
- // If the thumbnail is broken due to IOException, this will
- // fall back to default bitmap loading.
- LogUtil.e(LogUtil.BUGLE_IMAGE_TAG, "FileImageRequest: failed to load " +
- "thumbnail from Exif", ex);
- }
- }
- }
-
- // Fall back to default InputStream-based loading if no thumbnails could be retrieved.
- return super.loadBitmapInternal();
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java
deleted file mode 100644
index 00105f5..0000000
--- a/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.UriUtil;
-
-/**
- * Holds image request info about file system based image resource.
- */
-public class FileImageRequestDescriptor extends UriImageRequestDescriptor {
- public final String path;
-
- // Can we use the thumbnail image from Exif data?
- public final boolean canUseThumbnail;
-
- /**
- * Convenience constructor for when the image file's dimensions are not known.
- */
- public FileImageRequestDescriptor(final String path, final int desiredWidth,
- final int desiredHeight, final boolean canUseThumbnail, final boolean canCompress,
- final boolean isStatic) {
- this(path, desiredWidth, desiredHeight, FileImageRequest.UNSPECIFIED_SIZE,
- FileImageRequest.UNSPECIFIED_SIZE, canUseThumbnail, canCompress, isStatic);
- }
-
- /**
- * Creates a new file image request with this descriptor. Oftentimes image file metadata
- * has information such as the size of the image. Provide these metrics if they are known.
- */
- public FileImageRequestDescriptor(final String path, final int desiredWidth,
- final int desiredHeight, final int sourceWidth, final int sourceHeight,
- final boolean canUseThumbnail, final boolean canCompress, final boolean isStatic) {
- super(UriUtil.getUriForResourceFile(path), desiredWidth, desiredHeight, sourceWidth,
- sourceHeight, canCompress, isStatic, false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- this.path = path;
- this.canUseThumbnail = canUseThumbnail;
- }
-
- @Override
- public String getKey() {
- final String prefixKey = super.getKey();
- return prefixKey == null ? null : new StringBuilder(prefixKey).append(KEY_PART_DELIMITER)
- .append(canUseThumbnail).toString();
- }
-
- @Override
- public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
- return new FileImageRequest(context, this);
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/GifImageResource.java b/src/com/android/messaging/datamodel/media/GifImageResource.java
deleted file mode 100644
index d50cf47..0000000
--- a/src/com/android/messaging/datamodel/media/GifImageResource.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.media.ExifInterface;
-import android.support.rastermill.FrameSequence;
-import android.support.rastermill.FrameSequenceDrawable;
-
-import com.android.messaging.util.Assert;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public class GifImageResource extends ImageResource {
- private FrameSequence mFrameSequence;
-
- public GifImageResource(String key, FrameSequence frameSequence) {
- // GIF does not support exif tags
- super(key, ExifInterface.ORIENTATION_NORMAL);
- mFrameSequence = frameSequence;
- }
-
- public static GifImageResource createGifImageResource(String key, InputStream inputStream) {
- final FrameSequence frameSequence;
- try {
- frameSequence = FrameSequence.decodeStream(inputStream);
- } finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- // Nothing to do if we fail closing the stream
- }
- }
- if (frameSequence == null) {
- return null;
- }
- return new GifImageResource(key, frameSequence);
- }
-
- @Override
- public Drawable getDrawable(Resources resources) {
- return new FrameSequenceDrawable(mFrameSequence);
- }
-
- @Override
- public Bitmap getBitmap() {
- Assert.fail("GetBitmap() should never be called on a gif.");
- return null;
- }
-
- @Override
- public byte[] getBytes() {
- Assert.fail("GetBytes() should never be called on a gif.");
- return null;
- }
-
- @Override
- public Bitmap reuseBitmap() {
- return null;
- }
-
- @Override
- public boolean supportsBitmapReuse() {
- // FrameSequenceDrawable a.) takes two bitmaps and thus does not fit into the current
- // bitmap pool architecture b.) will rarely use bitmaps from one FrameSequenceDrawable to
- // the next that are the same sizes since they are used by attachments.
- return false;
- }
-
- @Override
- public int getMediaSize() {
- Assert.fail("GifImageResource should not be used by a media cache");
- // Only used by the media cache, which this does not use.
- return 0;
- }
-
- @Override
- public boolean isCacheable() {
- return false;
- }
-
- @Override
- protected void close() {
- acquireLock();
- try {
- if (mFrameSequence != null) {
- mFrameSequence = null;
- }
- } finally {
- releaseLock();
- }
- }
-
-}
diff --git a/src/com/android/messaging/datamodel/media/ImageRequest.java b/src/com/android/messaging/datamodel/media/ImageRequest.java
deleted file mode 100644
index ab8880d..0000000
--- a/src/com/android/messaging/datamodel/media/ImageRequest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RectF;
-
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.exif.ExifInterface;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * Base class that serves an image request for resolving, retrieving and decoding bitmap resources.
- *
- * Subclasses may choose to load images from different medium, such as from the file system or
- * from the local content resolver, by overriding the abstract getInputStreamForResource() method.
- */
-public abstract class ImageRequest<D extends ImageRequestDescriptor>
- implements MediaRequest<ImageResource> {
- public static final int UNSPECIFIED_SIZE = MessagePartData.UNSPECIFIED_SIZE;
-
- protected final Context mContext;
- protected final D mDescriptor;
- protected int mOrientation;
-
- /**
- * Creates a new image request with the given descriptor.
- */
- public ImageRequest(final Context context, final D descriptor) {
- mContext = context;
- mDescriptor = descriptor;
- }
-
- /**
- * Gets a key that uniquely identify the underlying image resource to be loaded (e.g. Uri or
- * file path).
- */
- @Override
- public String getKey() {
- return mDescriptor.getKey();
- }
-
- /**
- * Returns the image request descriptor attached to this request.
- */
- @Override
- public D getDescriptor() {
- return mDescriptor;
- }
-
- @Override
- public int getRequestType() {
- return MediaRequest.REQUEST_LOAD_MEDIA;
- }
-
- /**
- * Allows sub classes to specify that they want us to call getBitmapForResource rather than
- * getInputStreamForResource
- */
- protected boolean hasBitmapObject() {
- return false;
- }
-
- protected Bitmap getBitmapForResource() throws IOException {
- return null;
- }
-
- /**
- * Retrieves an input stream from which image resource could be loaded.
- * @throws FileNotFoundException
- */
- protected abstract InputStream getInputStreamForResource() throws FileNotFoundException;
-
- /**
- * Loads the image resource. This method is final; to override the media loading behavior
- * the subclass should override {@link #loadMediaInternal(List)}
- */
- @Override
- public final ImageResource loadMediaBlocking(List<MediaRequest<ImageResource>> chainedTask)
- throws IOException {
- Assert.isNotMainThread();
- final ImageResource loadedResource = loadMediaInternal(chainedTask);
- return postProcessOnBitmapResourceLoaded(loadedResource);
- }
-
- protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTask)
- throws IOException {
- if (!mDescriptor.isStatic() && isGif()) {
- final GifImageResource gifImageResource =
- GifImageResource.createGifImageResource(getKey(), getInputStreamForResource());
- if (gifImageResource == null) {
- throw new RuntimeException("Error decoding gif");
- }
- return gifImageResource;
- } else {
- final Bitmap loadedBitmap = loadBitmapInternal();
- if (loadedBitmap == null) {
- throw new RuntimeException("failed decoding bitmap");
- }
- return new DecodedImageResource(getKey(), loadedBitmap, mOrientation);
- }
- }
-
- protected boolean isGif() throws FileNotFoundException {
- return ImageUtils.isGif(getInputStreamForResource());
- }
-
- /**
- * The internal routine for loading the image. The caller may optionally provide the width
- * and height of the source image if known so that we don't need to manually decode those.
- */
- protected Bitmap loadBitmapInternal() throws IOException {
-
- final boolean unknownSize = mDescriptor.sourceWidth == UNSPECIFIED_SIZE ||
- mDescriptor.sourceHeight == UNSPECIFIED_SIZE;
-
- // If the ImageRequest has a Bitmap object rather than a stream, there's little to do here
- if (hasBitmapObject()) {
- final Bitmap bitmap = getBitmapForResource();
- if (bitmap != null && unknownSize) {
- mDescriptor.updateSourceDimensions(bitmap.getWidth(), bitmap.getHeight());
- }
- return bitmap;
- }
-
- mOrientation = ImageUtils.getOrientation(getInputStreamForResource());
-
- final BitmapFactory.Options options = PoolableImageCache.getBitmapOptionsForPool(
- false /* scaled */, 0 /* inputDensity */, 0 /* targetDensity */);
- // First, check dimensions of the bitmap if not already known.
- if (unknownSize) {
- final InputStream inputStream = getInputStreamForResource();
- if (inputStream != null) {
- try {
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(inputStream, null, options);
- // This is called when dimensions of image were unknown to allow db update
- if (ExifInterface.getOrientationParams(mOrientation).invertDimensions) {
- mDescriptor.updateSourceDimensions(options.outHeight, options.outWidth);
- } else {
- mDescriptor.updateSourceDimensions(options.outWidth, options.outHeight);
- }
- } finally {
- inputStream.close();
- }
- } else {
- throw new FileNotFoundException();
- }
- } else {
- options.outWidth = mDescriptor.sourceWidth;
- options.outHeight = mDescriptor.sourceHeight;
- }
-
- // Calculate inSampleSize
- options.inSampleSize = ImageUtils.get().calculateInSampleSize(options,
- mDescriptor.desiredWidth, mDescriptor.desiredHeight);
- Assert.isTrue(options.inSampleSize > 0);
-
- // Reopen the input stream and actually decode the bitmap. The initial
- // BitmapFactory.decodeStream() reads the header portion of the bitmap stream and leave
- // the input stream at the last read position. Since this input stream doesn't support
- // mark() and reset(), the only viable way to reload the input stream is to re-open it.
- // Alternatively, we could decode the bitmap into a byte array first and act on the byte
- // array, but that also means the entire bitmap (for example a 10MB image from the gallery)
- // without downsampling will have to be loaded into memory up front, which we don't want
- // as it gives a much bigger possibility of OOM when handling big images. Therefore, the
- // solution here is to close and reopen the bitmap input stream.
- // For inline images the size is cached in DB and this hit is only taken once per image
- final InputStream inputStream = getInputStreamForResource();
- if (inputStream != null) {
- try {
- options.inJustDecodeBounds = false;
-
- // Actually decode the bitmap, optionally using the bitmap pool.
- final ReusableImageResourcePool bitmapPool = getBitmapPool();
- if (bitmapPool == null) {
- return BitmapFactory.decodeStream(inputStream, null, options);
- } else {
- final int sampledWidth = (options.outWidth + options.inSampleSize - 1) /
- options.inSampleSize;
- final int sampledHeight = (options.outHeight + options.inSampleSize - 1) /
- options.inSampleSize;
- return bitmapPool.decodeSampledBitmapFromInputStream(
- inputStream, options, sampledWidth, sampledHeight);
- }
- } finally {
- inputStream.close();
- }
- } else {
- throw new FileNotFoundException();
- }
- }
-
- private ImageResource postProcessOnBitmapResourceLoaded(final ImageResource loadedResource) {
- if (mDescriptor.cropToCircle && loadedResource instanceof DecodedImageResource) {
- final int width = mDescriptor.desiredWidth;
- final int height = mDescriptor.desiredHeight;
- final Bitmap sourceBitmap = loadedResource.getBitmap();
- final Bitmap targetBitmap = getBitmapPool().createOrReuseBitmap(width, height);
- final RectF dest = new RectF(0, 0, width, height);
- final RectF source = new RectF(0, 0, sourceBitmap.getWidth(), sourceBitmap.getHeight());
- final int backgroundColor = mDescriptor.circleBackgroundColor;
- final int strokeColor = mDescriptor.circleStrokeColor;
- ImageUtils.drawBitmapWithCircleOnCanvas(sourceBitmap, new Canvas(targetBitmap), source,
- dest, null, backgroundColor == 0 ? false : true /* fillBackground */,
- backgroundColor, strokeColor);
- return new DecodedImageResource(getKey(), targetBitmap,
- loadedResource.getOrientation());
- }
- return loadedResource;
- }
-
- /**
- * Returns the bitmap pool for this image request.
- */
- protected ReusableImageResourcePool getBitmapPool() {
- return MediaCacheManager.get().getOrCreateBitmapPoolForCache(getCacheId());
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public MediaCache<ImageResource> getMediaCache() {
- return (MediaCache<ImageResource>) MediaCacheManager.get().getOrCreateMediaCacheById(
- getCacheId());
- }
-
- /**
- * Returns the cache id. Subclasses may override this to use a different cache.
- */
- @Override
- public int getCacheId() {
- return BugleMediaCacheManager.DEFAULT_IMAGE_CACHE;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java
deleted file mode 100644
index 20cb9af..0000000
--- a/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-
-import com.android.messaging.util.Assert;
-
-/**
- * The base ImageRequest descriptor that describes the requirement of the requested image
- * resource, including the desired size. It holds request info that will be consumed by
- * ImageRequest instances. Subclasses of ImageRequest are expected to take
- * more descriptions such as content URI or file path.
- */
-public abstract class ImageRequestDescriptor extends MediaRequestDescriptor<ImageResource> {
- /** Desired size for the image (if known). This is used for bitmap downsampling */
- public final int desiredWidth;
- public final int desiredHeight;
-
- /** Source size of the image (if known). This is used so that we don't have to manually decode
- * the metrics from the image resource */
- public final int sourceWidth;
- public final int sourceHeight;
-
- /**
- * A static image resource is required, even if the image format supports animation (like Gif).
- */
- public final boolean isStatic;
-
- /**
- * The loaded image will be cropped to circular shape.
- */
- public final boolean cropToCircle;
-
- /**
- * The loaded image will be cropped to circular shape with the background color.
- */
- public final int circleBackgroundColor;
-
- /**
- * The loaded image will be cropped to circular shape with a stroke color.
- */
- public final int circleStrokeColor;
-
- protected static final char KEY_PART_DELIMITER = '|';
-
- /**
- * Creates a new image request with unspecified width and height. In this case, the full
- * bitmap is loaded and decoded, so unless you are sure that the image will be of
- * reasonable size, you should consider limiting at least one of the two dimensions
- * (for example, limiting the image width to the width of the ImageView container).
- */
- public ImageRequestDescriptor() {
- this(ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE,
- ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false, false, 0, 0);
- }
-
- public ImageRequestDescriptor(final int desiredWidth, final int desiredHeight) {
- this(desiredWidth, desiredHeight,
- ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false, false, 0, 0);
- }
-
- public ImageRequestDescriptor(final int desiredWidth,
- final int desiredHeight, final int sourceWidth, final int sourceHeight,
- final boolean isStatic, final boolean cropToCircle, final int circleBackgroundColor,
- int circleStrokeColor) {
- Assert.isTrue(desiredWidth == ImageRequest.UNSPECIFIED_SIZE || desiredWidth > 0);
- Assert.isTrue(desiredHeight == ImageRequest.UNSPECIFIED_SIZE || desiredHeight > 0);
- Assert.isTrue(sourceWidth == ImageRequest.UNSPECIFIED_SIZE || sourceWidth > 0);
- Assert.isTrue(sourceHeight == ImageRequest.UNSPECIFIED_SIZE || sourceHeight > 0);
- this.desiredWidth = desiredWidth;
- this.desiredHeight = desiredHeight;
- this.sourceWidth = sourceWidth;
- this.sourceHeight = sourceHeight;
- this.isStatic = isStatic;
- this.cropToCircle = cropToCircle;
- this.circleBackgroundColor = circleBackgroundColor;
- this.circleStrokeColor = circleStrokeColor;
- }
-
- public String getKey() {
- return new StringBuilder()
- .append(desiredWidth).append(KEY_PART_DELIMITER)
- .append(desiredHeight).append(KEY_PART_DELIMITER)
- .append(String.valueOf(cropToCircle)).append(KEY_PART_DELIMITER)
- .append(String.valueOf(circleBackgroundColor)).append(KEY_PART_DELIMITER)
- .append(String.valueOf(isStatic)).toString();
- }
-
- public boolean isStatic() {
- return isStatic;
- }
-
- @Override
- public abstract MediaRequest<ImageResource> buildSyncMediaRequest(Context context);
-
- // Called once source dimensions finally determined upon loading the image
- public void updateSourceDimensions(final int sourceWidth, final int sourceHeight) {
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/ImageResource.java b/src/com/android/messaging/datamodel/media/ImageResource.java
deleted file mode 100644
index 75d817d..0000000
--- a/src/com/android/messaging/datamodel/media/ImageResource.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-
-/**
- * Base class for holding some form of image resource. The subclass gets to define the specific
- * type of data format it's holding, whether it be Bitmap objects or compressed byte arrays.
- */
-public abstract class ImageResource extends RefCountedMediaResource {
- protected final int mOrientation;
-
- public ImageResource(final String key, int orientation) {
- super(key);
- mOrientation = orientation;
- }
-
- /**
- * Gets the contained image in drawable format.
- */
- public abstract Drawable getDrawable(Resources resources);
-
- /**
- * Gets the contained image in bitmap format.
- */
- public abstract Bitmap getBitmap();
-
- /**
- * Gets the contained image in byte array format.
- */
- public abstract byte[] getBytes();
-
- /**
- * Attempt to reuse the bitmap in the image resource and re-purpose it for something else.
- * After this, the image resource will relinquish ownership on the bitmap resource so that
- * it doesn't try to recycle it when getting closed.
- */
- public abstract Bitmap reuseBitmap();
- public abstract boolean supportsBitmapReuse();
-
- /**
- * Gets the orientation of the image as one of the ExifInterface.ORIENTATION_* constants
- */
- public int getOrientation() {
- return mOrientation;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/MediaBytes.java b/src/com/android/messaging/datamodel/media/MediaBytes.java
deleted file mode 100644
index 823bf27..0000000
--- a/src/com/android/messaging/datamodel/media/MediaBytes.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-
-/**
- * Container class for handing around media information used by the MediaResourceManager.
- */
-public class MediaBytes extends RefCountedMediaResource {
- private final byte[] mBytes;
-
- public MediaBytes(final String key, final byte[] bytes) {
- super(key);
- mBytes = bytes;
- }
-
- public byte[] getMediaBytes() {
- return mBytes;
- }
-
- @Override
- public int getMediaSize() {
- return mBytes.length;
- }
-
- @Override
- protected void close() {
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/MediaCache.java b/src/com/android/messaging/datamodel/media/MediaCache.java
deleted file mode 100644
index 510da2d..0000000
--- a/src/com/android/messaging/datamodel/media/MediaCache.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.util.LruCache;
-
-import com.android.messaging.util.LogUtil;
-
-/**
- * A modified LruCache that is able to hold RefCountedMediaResource instances. It releases
- * ref on the entries as they are evicted from the cache, and it uses the media resource
- * size in kilobytes, instead of the entry count, as the size of the cache.
- *
- * This class is used by the MediaResourceManager class to maintain a number of caches for
- * holding different types of {@link RefCountedMediaResource}
- */
-public class MediaCache<T extends RefCountedMediaResource> extends LruCache<String, T> {
- private static final String TAG = LogUtil.BUGLE_IMAGE_TAG;
-
- // Default memory cache size in kilobytes
- protected static final int DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES = 1024 * 5; // 5MB
-
- // Unique identifier for the cache.
- private final int mId;
- // Descriptive name given to the cache for debugging purposes.
- private final String mName;
-
- // Convenience constructor that uses the default cache size.
- public MediaCache(final int id, final String name) {
- this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name);
- }
-
- public MediaCache(final int maxSize, final int id, final String name) {
- super(maxSize);
- mId = id;
- mName = name;
- }
-
- public void destroy() {
- evictAll();
- }
-
- public String getName() {
- return mName;
- }
-
- public int getId() {
- return mId;
- }
-
- /**
- * Gets a media resource from this cache. Must use this method to get resource instead of get()
- * to ensure addRef() on the resource.
- */
- public synchronized T fetchResourceFromCache(final String key) {
- final T ret = get(key);
- if (ret != null) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "cache hit in mediaCache @ " + getName() +
- ", total cache hit = " + hitCount() +
- ", total cache miss = " + missCount());
- }
- ret.addRef();
- } else if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "cache miss in mediaCache @ " + getName() +
- ", total cache hit = " + hitCount() +
- ", total cache miss = " + missCount());
- }
- return ret;
- }
-
- /**
- * Add a media resource to this cache. Must use this method to add resource instead of put()
- * to ensure addRef() on the resource.
- */
- public synchronized T addResourceToCache(final String key, final T mediaResource) {
- mediaResource.addRef();
- return put(key, mediaResource);
- }
-
- /**
- * Notify the removed entry that is no longer being cached
- */
- @Override
- protected synchronized void entryRemoved(final boolean evicted, final String key,
- final T oldValue, final T newValue) {
- oldValue.release();
- }
-
- /**
- * Measure item size in kilobytes rather than units which is more practical
- * for a media resource cache
- */
- @Override
- protected int sizeOf(final String key, final T value) {
- final int mediaSizeInKilobytes = value.getMediaSize() / 1024;
- // Never zero-count any resource, count as at least 1KB.
- return mediaSizeInKilobytes == 0 ? 1 : mediaSizeInKilobytes;
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/MediaCacheManager.java b/src/com/android/messaging/datamodel/media/MediaCacheManager.java
deleted file mode 100644
index 6e029f2..0000000
--- a/src/com/android/messaging/datamodel/media/MediaCacheManager.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.util.SparseArray;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.MemoryCacheManager;
-import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache;
-import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool;
-
-/**
- * Manages a set of media caches by id.
- */
-public abstract class MediaCacheManager implements MemoryCache {
- public static MediaCacheManager get() {
- return Factory.get().getMediaCacheManager();
- }
-
- protected final SparseArray<MediaCache<?>> mCaches;
-
- public MediaCacheManager() {
- mCaches = new SparseArray<MediaCache<?>>();
- MemoryCacheManager.get().registerMemoryCache(this);
- }
-
- @Override
- public void reclaim() {
- final int count = mCaches.size();
- for (int i = 0; i < count; i++) {
- mCaches.valueAt(i).destroy();
- }
- mCaches.clear();
- }
-
- public synchronized MediaCache<?> getOrCreateMediaCacheById(final int id) {
- MediaCache<?> cache = mCaches.get(id);
- if (cache == null) {
- cache = createMediaCacheById(id);
- if (cache != null) {
- mCaches.put(id, cache);
- }
- }
- return cache;
- }
-
- public ReusableImageResourcePool getOrCreateBitmapPoolForCache(final int cacheId) {
- final MediaCache<?> cache = getOrCreateMediaCacheById(cacheId);
- if (cache != null && cache instanceof PoolableImageCache) {
- return ((PoolableImageCache) cache).asReusableBitmapPool();
- }
- return null;
- }
-
- protected abstract MediaCache<?> createMediaCacheById(final int id);
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/MediaRequest.java b/src/com/android/messaging/datamodel/media/MediaRequest.java
deleted file mode 100644
index 703671b..0000000
--- a/src/com/android/messaging/datamodel/media/MediaRequest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import java.util.List;
-
-/**
- * Keeps track of a media loading request. MediaResourceManager uses this interface to load, encode,
- * decode, and cache different types of media resource.
- *
- * This interface defines a media request class that's threading-model-blind. Wrapper classes
- * (such as {@link AsyncMediaRequestWrapper} wraps around any base media request to offer async
- * extensions).
- */
-public interface MediaRequest<T extends RefCountedMediaResource> {
- public static final int REQUEST_ENCODE_MEDIA = 1;
- public static final int REQUEST_DECODE_MEDIA = 2;
- public static final int REQUEST_LOAD_MEDIA = 3;
-
- /**
- * Returns a unique key used for storing and looking up the MediaRequest.
- */
- String getKey();
-
- /**
- * This method performs the heavy-lifting work of synchronously loading the media bytes for
- * this MediaRequest on a single threaded executor.
- * @param chainedTask subsequent tasks to be performed after this request is complete. For
- * example, an image request may need to compress the image resource before putting it in the
- * cache
- */
- T loadMediaBlocking(List<MediaRequest<T>> chainedTask) throws Exception;
-
- /**
- * Returns the media cache where this MediaRequest wants to store the loaded
- * media resource.
- */
- MediaCache<T> getMediaCache();
-
- /**
- * Returns the id of the cache where this MediaRequest wants to store the loaded
- * media resource.
- */
- int getCacheId();
-
- /**
- * Returns the request type of this media request, i.e. one of {@link #REQUEST_ENCODE_MEDIA},
- * {@link #REQUEST_DECODE_MEDIA}, or {@link #REQUEST_LOAD_MEDIA}. The default is
- * {@link #REQUEST_LOAD_MEDIA}
- */
- int getRequestType();
-
- /**
- * Returns the descriptor defining the request.
- */
- MediaRequestDescriptor<T> getDescriptor();
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java
deleted file mode 100644
index 216b2a3..0000000
--- a/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-
-/**
- * The base data holder/builder class for constructing async/sync MediaRequest objects during
- * runtime.
- */
-public abstract class MediaRequestDescriptor<T extends RefCountedMediaResource> {
- public abstract MediaRequest<T> buildSyncMediaRequest(Context context);
-
- /**
- * Builds an async media request to be used with
- * {@link MediaResourceManager#requestMediaResourceAsync(MediaRequest)}
- */
- public BindableMediaRequest<T> buildAsyncMediaRequest(final Context context,
- final MediaResourceLoadListener<T> listener) {
- final MediaRequest<T> syncRequest = buildSyncMediaRequest(context);
- return AsyncMediaRequestWrapper.createWith(syncRequest, listener);
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/MediaResourceManager.java b/src/com/android/messaging/datamodel/media/MediaResourceManager.java
deleted file mode 100644
index 13f7291..0000000
--- a/src/com/android/messaging/datamodel/media/MediaResourceManager.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.os.AsyncTask;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.RunsOnAnyThread;
-import com.android.messaging.util.LogUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
-/**
- * <p>Loads and maintains a set of in-memory LRU caches for different types of media resources.
- * Right now we don't utilize any disk cache as all media urls are expected to be resolved to
- * local content.<p/>
- *
- * <p>The MediaResourceManager takes media loading requests through one of two ways:</p>
- *
- * <ol>
- * <li>{@link #requestMediaResourceAsync(MediaRequest)} that takes a MediaRequest, which may be a
- * regular request if the caller doesn't want to listen for events (fire-and-forget),
- * or an async request wrapper if event callback is needed.</li>
- * <li>{@link #requestMediaResourceSync(MediaRequest)} which takes a MediaRequest and synchronously
- * returns the loaded result, or null if failed.</li>
- * </ol>
- *
- * <p>For each media loading task, MediaResourceManager starts an AsyncTask that runs on a
- * dedicated thread, which calls MediaRequest.loadMediaBlocking() to perform the actual media
- * loading work. As the media resources are loaded, MediaResourceManager notifies the callers
- * (which must implement the MediaResourceLoadListener interface) via onMediaResourceLoaded()
- * callback. Meanwhile, MediaResourceManager also pushes the loaded resource onto its dedicated
- * cache.</p>
- *
- * <p>The media resource caches ({@link MediaCache}) are maintained as a set of LRU caches. They are
- * created on demand by the incoming MediaRequest's getCacheId() method. The implementations of
- * MediaRequest (such as {@link ImageRequest}) get to determine the desired cache id. For Bugle,
- * the list of available caches are in {@link BugleMediaCacheManager}</p>
- *
- * <p>Optionally, media loading can support on-demand media encoding and decoding.
- * All {@link MediaRequest}'s can opt to chain additional {@link MediaRequest}'s to be executed
- * after the completion of the main media loading task, by adding new tasks to the chained
- * task list in {@link MediaRequest#loadMediaBlocking(List)}. One possible type of chained task is
- * media encoding task. Loaded media will be encoded on a dedicated single threaded executor
- * *after* the UI is notified of the loaded media. In this case, the encoded media resource will
- * be eventually pushed to the cache, which will later be decoded before posting to the UI thread
- * on cache hit.</p>
- *
- * <p><b>To add support for a new type of media resource,</b></p>
- *
- * <ol>
- * <li>Create a new subclass of {@link RefCountedMediaResource} for the new resource type (example:
- * {@link ImageResource} class).</li>
- *
- * <li>Implement the {@link MediaRequest} interface (example: {@link ImageRequest}). Perform the
- * media loading work in loadMediaBlocking() and return a cache id in getCacheId().</li>
- *
- * <li>For the UI component that requests the media resource, let it implement
- * {@link MediaResourceLoadListener} interface to listen for resource load callback. Let the
- * UI component call MediaResourceManager.requestMediaResourceAsync() to request a media source.
- * (example: {@link com.android.messaging.ui.ContactIconView}</li>
- * </ol>
- */
-public class MediaResourceManager {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- public static MediaResourceManager get() {
- return Factory.get().getMediaResourceManager();
- }
-
- /**
- * Listener for asynchronous callback from media loading events.
- */
- public interface MediaResourceLoadListener<T extends RefCountedMediaResource> {
- void onMediaResourceLoaded(MediaRequest<T> request, T resource, boolean cached);
- void onMediaResourceLoadError(MediaRequest<T> request, Exception exception);
- }
-
- // We use a fixed thread pool for handling media loading tasks. Using a cached thread pool
- // allows for unlimited thread creation which can lead to OOMs so we limit the threads here.
- private static final Executor MEDIA_LOADING_EXECUTOR = Executors.newFixedThreadPool(10);
-
- // A dedicated single thread executor for performing background task after loading the resource
- // on the media loading executor. This includes work such as encoding loaded media to be cached.
- // These tasks are run on a single worker thread with low priority so as not to contend with the
- // media loading tasks.
- private static final Executor MEDIA_BACKGROUND_EXECUTOR = Executors.newSingleThreadExecutor(
- new ThreadFactory() {
- @Override
- public Thread newThread(final Runnable runnable) {
- final Thread encodingThread = new Thread(runnable);
- encodingThread.setPriority(Thread.MIN_PRIORITY);
- return encodingThread;
- }
- });
-
- /**
- * Requests a media resource asynchronously. Upon completion of the media loading task,
- * the listener will be notified of success/failure iff it's still bound. A refcount on the
- * resource is held and guaranteed for the caller for the duration of the
- * {@link MediaResourceLoadListener#onMediaResourceLoaded(
- * MediaRequest, RefCountedMediaResource, boolean)} callback.
- * @param mediaRequest the media request. May be either an
- * {@link AsyncMediaRequestWrapper} for listening for event callbacks, or a regular media
- * request for fire-and-forget type of behavior.
- */
- public <T extends RefCountedMediaResource> void requestMediaResourceAsync(
- final MediaRequest<T> mediaRequest) {
- scheduleAsyncMediaRequest(mediaRequest, MEDIA_LOADING_EXECUTOR);
- }
-
- /**
- * Requests a media resource synchronously.
- * @return the loaded resource with a refcount reserved for the caller. The caller must call
- * release() on the resource once it's done using it (like with Cursors).
- */
- public <T extends RefCountedMediaResource> T requestMediaResourceSync(
- final MediaRequest<T> mediaRequest) {
- Assert.isNotMainThread();
- // Block and load media.
- MediaLoadingResult<T> loadResult = null;
- try {
- loadResult = processMediaRequestInternal(mediaRequest);
- // The loaded resource should have at least one refcount by now reserved for the caller.
- Assert.isTrue(loadResult.loadedResource.getRefCount() > 0);
- return loadResult.loadedResource;
- } catch (final Exception e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Synchronous media loading failed, key=" +
- mediaRequest.getKey(), e);
- return null;
- } finally {
- if (loadResult != null) {
- // Schedule the background requests chained to the main request.
- loadResult.scheduleChainedRequests();
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- private <T extends RefCountedMediaResource> MediaLoadingResult<T> processMediaRequestInternal(
- final MediaRequest<T> mediaRequest)
- throws Exception {
- final List<MediaRequest<T>> chainedRequests = new ArrayList<>();
- T loadedResource = null;
- // Try fetching from cache first.
- final T cachedResource = loadMediaFromCache(mediaRequest);
- if (cachedResource != null) {
- if (cachedResource.isEncoded()) {
- // The resource is encoded, issue a decoding request.
- final MediaRequest<T> decodeRequest = (MediaRequest<T>) cachedResource
- .getMediaDecodingRequest(mediaRequest);
- Assert.notNull(decodeRequest);
- cachedResource.release();
- loadedResource = loadMediaFromRequest(decodeRequest, chainedRequests);
- } else {
- // The resource is ready-to-use.
- loadedResource = cachedResource;
- }
- } else {
- // Actually load the media after cache miss.
- loadedResource = loadMediaFromRequest(mediaRequest, chainedRequests);
- }
- return new MediaLoadingResult<>(loadedResource, cachedResource != null /* fromCache */,
- chainedRequests);
- }
-
- private <T extends RefCountedMediaResource> T loadMediaFromCache(
- final MediaRequest<T> mediaRequest) {
- if (mediaRequest.getRequestType() != MediaRequest.REQUEST_LOAD_MEDIA) {
- // Only look up in the cache if we are loading media.
- return null;
- }
- final MediaCache<T> mediaCache = mediaRequest.getMediaCache();
- if (mediaCache != null) {
- final T mediaResource = mediaCache.fetchResourceFromCache(mediaRequest.getKey());
- if (mediaResource != null) {
- return mediaResource;
- }
- }
- return null;
- }
-
- private <T extends RefCountedMediaResource> T loadMediaFromRequest(
- final MediaRequest<T> mediaRequest, final List<MediaRequest<T>> chainedRequests)
- throws Exception {
- final T resource = mediaRequest.loadMediaBlocking(chainedRequests);
- // mediaRequest.loadMediaBlocking() should never return null without
- // throwing an exception.
- Assert.notNull(resource);
- // It's possible for the media to be evicted right after it's added to
- // the cache (possibly because it's by itself too big for the cache).
- // It's also possible that, after added to the cache, something else comes
- // to the cache and evicts this media resource. To prevent this from
- // recycling the underlying resource objects, make sure to add ref before
- // adding to cache so that the caller is guaranteed a ref on the resource.
- resource.addRef();
- // Don't cache the media request if it is defined as non-cacheable.
- if (resource.isCacheable()) {
- addResourceToMemoryCache(mediaRequest, resource);
- }
- return resource;
- }
-
- /**
- * Schedule an async media request on the given <code>executor</code>.
- * @param mediaRequest the media request to be processed asynchronously. May be either an
- * {@link AsyncMediaRequestWrapper} for listening for event callbacks, or a regular media
- * request for fire-and-forget type of behavior.
- */
- private <T extends RefCountedMediaResource> void scheduleAsyncMediaRequest(
- final MediaRequest<T> mediaRequest, final Executor executor) {
- final BindableMediaRequest<T> bindableRequest =
- (mediaRequest instanceof BindableMediaRequest<?>) ?
- (BindableMediaRequest<T>) mediaRequest : null;
- if (bindableRequest != null && !bindableRequest.isBound()) {
- return; // Request is obsolete
- }
- // We don't use SafeAsyncTask here since it enforces the shared thread pool executor
- // whereas we want a dedicated thread pool executor.
- AsyncTask<Void, Void, MediaLoadingResult<T>> mediaLoadingTask =
- new AsyncTask<Void, Void, MediaLoadingResult<T>>() {
- private Exception mException;
-
- @Override
- protected MediaLoadingResult<T> doInBackground(Void... params) {
- // Double check the request is still valid by the time we start processing it
- if (bindableRequest != null && !bindableRequest.isBound()) {
- return null; // Request is obsolete
- }
- try {
- return processMediaRequestInternal(mediaRequest);
- } catch (Exception e) {
- mException = e;
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(final MediaLoadingResult<T> result) {
- if (result != null) {
- Assert.isNull(mException);
- Assert.isTrue(result.loadedResource.getRefCount() > 0);
- try {
- if (bindableRequest != null) {
- bindableRequest.onMediaResourceLoaded(
- bindableRequest, result.loadedResource, result.fromCache);
- }
- } finally {
- result.loadedResource.release();
- result.scheduleChainedRequests();
- }
- } else if (mException != null) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Asynchronous media loading failed, key=" +
- mediaRequest.getKey(), mException);
- if (bindableRequest != null) {
- bindableRequest.onMediaResourceLoadError(bindableRequest, mException);
- }
- } else {
- Assert.isTrue(bindableRequest == null || !bindableRequest.isBound());
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "media request not processed, no longer bound; key=" +
- LogUtil.sanitizePII(mediaRequest.getKey()) /* key with phone# */);
- }
- }
- }
- };
- mediaLoadingTask.executeOnExecutor(executor, (Void) null);
- }
-
- @VisibleForTesting
- @RunsOnAnyThread
- <T extends RefCountedMediaResource> void addResourceToMemoryCache(
- final MediaRequest<T> mediaRequest, final T mediaResource) {
- Assert.isTrue(mediaResource != null);
- final MediaCache<T> mediaCache = mediaRequest.getMediaCache();
- if (mediaCache != null) {
- mediaCache.addResourceToCache(mediaRequest.getKey(), mediaResource);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "added media resource to " + mediaCache.getName() + ". key=" +
- LogUtil.sanitizePII(mediaRequest.getKey()) /* key can contain phone# */);
- }
- }
- }
-
- private class MediaLoadingResult<T extends RefCountedMediaResource> {
- public final T loadedResource;
- public final boolean fromCache;
- private final List<MediaRequest<T>> mChainedRequests;
-
- MediaLoadingResult(final T loadedResource, final boolean fromCache,
- final List<MediaRequest<T>> chainedRequests) {
- this.loadedResource = loadedResource;
- this.fromCache = fromCache;
- mChainedRequests = chainedRequests;
- }
-
- /**
- * Asynchronously schedule a list of chained requests on the background thread.
- */
- public void scheduleChainedRequests() {
- for (final MediaRequest<T> mediaRequest : mChainedRequests) {
- scheduleAsyncMediaRequest(mediaRequest, MEDIA_BACKGROUND_EXECUTOR);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java
deleted file mode 100644
index 1871e66..0000000
--- a/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.media;
-
-import android.net.Uri;
-
-import com.android.messaging.datamodel.action.UpdateMessagePartSizeAction;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.util.ImageUtils;
-
-/**
- * Image descriptor attached to a message part.
- * Once image size is determined during loading this descriptor will update the db if necessary.
- */
-public class MessagePartImageRequestDescriptor extends UriImageRequestDescriptor {
- private final String mMessagePartId;
-
- /**
- * Creates a new image request for a message part.
- */
- public MessagePartImageRequestDescriptor(final MessagePartData messagePart,
- final int desiredWidth, final int desiredHeight, boolean isStatic) {
- // Pull image parameters out of the MessagePart record
- this(messagePart.getPartId(), messagePart.getContentUri(), desiredWidth, desiredHeight,
- messagePart.getWidth(), messagePart.getHeight(), isStatic);
- }
-
- protected MessagePartImageRequestDescriptor(final String messagePartId, final Uri contentUri,
- final int desiredWidth, final int desiredHeight, final int sourceWidth,
- final int sourceHeight, boolean isStatic) {
- super(contentUri, desiredWidth, desiredHeight, sourceWidth, sourceHeight,
- true /* allowCompression */, isStatic, false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- mMessagePartId = messagePartId;
- }
-
- @Override
- public void updateSourceDimensions(final int updatedWidth, final int updatedHeight) {
- // If the dimensions of the image do not match then queue a DB update with new size.
- // Don't update if we don't have a part id, which happens if this part is loaded as
- // draft through actions such as share intent/message forwarding.
- if (mMessagePartId != null &&
- updatedWidth != MessagePartData.UNSPECIFIED_SIZE &&
- updatedHeight != MessagePartData.UNSPECIFIED_SIZE &&
- updatedWidth != sourceWidth && updatedHeight != sourceHeight) {
- UpdateMessagePartSizeAction.updateSize(mMessagePartId, updatedWidth, updatedHeight);
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java
deleted file mode 100644
index ff11e92..0000000
--- a/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.net.Uri;
-
-import com.android.messaging.datamodel.data.MessagePartData;
-
-public class MessagePartVideoThumbnailRequestDescriptor extends MessagePartImageRequestDescriptor {
- public MessagePartVideoThumbnailRequestDescriptor(MessagePartData messagePart) {
- super(messagePart, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false);
- }
-
- public MessagePartVideoThumbnailRequestDescriptor(Uri uri) {
- super(null, uri, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE,
- ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false);
- }
-
- @Override
- public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
- return new VideoThumbnailRequest(context, this);
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java b/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java
deleted file mode 100644
index 642e947..0000000
--- a/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * Serves network content URI based image requests.
- */
-public class NetworkUriImageRequest<D extends UriImageRequestDescriptor> extends
- ImageRequest<D> {
-
- public NetworkUriImageRequest(Context context, D descriptor) {
- super(context, descriptor);
- mOrientation = android.media.ExifInterface.ORIENTATION_UNDEFINED;
- }
-
- @Override
- protected InputStream getInputStreamForResource() throws FileNotFoundException {
- Assert.isNotMainThread();
- // Since we need to have an open urlConnection to get the stream, but we don't want to keep
- // that connection open. There is no good way to perform this method.
- return null;
- }
-
- @Override
- protected boolean isGif() throws FileNotFoundException {
- Assert.isNotMainThread();
-
- HttpURLConnection connection = null;
- try {
- final URL url = new URL(mDescriptor.uri.toString());
- connection = (HttpURLConnection) url.openConnection();
- connection.connect();
- if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
- return ContentType.IMAGE_GIF.equalsIgnoreCase(connection.getContentType());
- }
- } catch (MalformedURLException e) {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "MalformedUrl for image with url: "
- + mDescriptor.uri.toString(), e);
- } catch (IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "IOException trying to get inputStream for image with url: "
- + mDescriptor.uri.toString(), e);
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- return false;
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public Bitmap loadBitmapInternal() throws IOException {
- Assert.isNotMainThread();
-
- InputStream inputStream = null;
- Bitmap bitmap = null;
- HttpURLConnection connection = null;
- try {
- final URL url = new URL(mDescriptor.uri.toString());
- connection = (HttpURLConnection) url.openConnection();
- connection.setDoInput(true);
- connection.connect();
- if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
- bitmap = BitmapFactory.decodeStream(connection.getInputStream());
- }
- } catch (MalformedURLException e) {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "MalformedUrl for image with url: "
- + mDescriptor.uri.toString(), e);
- } catch (final OutOfMemoryError e) {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "OutOfMemoryError for image with url: "
- + mDescriptor.uri.toString(), e);
- Factory.get().reclaimMemory();
- } catch (IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "IOException trying to get inputStream for image with url: "
- + mDescriptor.uri.toString(), e);
- } finally {
- if (inputStream != null) {
- inputStream.close();
- }
- if (connection != null) {
- connection.disconnect();
- }
- }
- return bitmap;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/PoolableImageCache.java b/src/com/android/messaging/datamodel/media/PoolableImageCache.java
deleted file mode 100644
index df814ba..0000000
--- a/src/com/android/messaging/datamodel/media/PoolableImageCache.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.os.SystemClock;
-import android.support.annotation.NonNull;
-import android.util.SparseArray;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.LinkedList;
-
-/**
- * A media cache that holds image resources, which doubles as a bitmap pool that allows the
- * consumer to optionally decode image resources using unused bitmaps stored in the cache.
- */
-public class PoolableImageCache extends MediaCache<ImageResource> {
- private static final int MIN_TIME_IN_POOL = 5000;
-
- /** Encapsulates bitmap pool representation of the image cache */
- private final ReusableImageResourcePool mReusablePoolAccessor = new ReusableImageResourcePool();
-
- public PoolableImageCache(final int id, final String name) {
- this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name);
- }
-
- public PoolableImageCache(final int maxSize, final int id, final String name) {
- super(maxSize, id, name);
- }
-
- /**
- * Creates a new BitmapFactory.Options for using the self-contained bitmap pool.
- */
- public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled,
- final int inputDensity, final int targetDensity) {
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inScaled = scaled;
- options.inDensity = inputDensity;
- options.inTargetDensity = targetDensity;
- options.inSampleSize = 1;
- options.inJustDecodeBounds = false;
- options.inMutable = true;
- return options;
- }
-
- @Override
- public synchronized ImageResource addResourceToCache(final String key,
- final ImageResource imageResource) {
- mReusablePoolAccessor.onResourceEnterCache(imageResource);
- return super.addResourceToCache(key, imageResource);
- }
-
- @Override
- protected synchronized void entryRemoved(final boolean evicted, final String key,
- final ImageResource oldValue, final ImageResource newValue) {
- mReusablePoolAccessor.onResourceLeaveCache(oldValue);
- super.entryRemoved(evicted, key, oldValue, newValue);
- }
-
- /**
- * Returns a representation of the image cache as a reusable bitmap pool.
- */
- public ReusableImageResourcePool asReusableBitmapPool() {
- return mReusablePoolAccessor;
- }
-
- /**
- * A bitmap pool representation built on top of the image cache. It treats the image resources
- * stored in the image cache as a self-contained bitmap pool and is able to create or
- * reclaim bitmap resource as needed.
- */
- public class ReusableImageResourcePool {
- private static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF;
- private static final int INVALID_POOL_KEY = 0;
-
- /**
- * Number of reuse failures to skip before reporting.
- * For debugging purposes, change to a lower number for more frequent reporting.
- */
- private static final int FAILED_REPORTING_FREQUENCY = 100;
-
- /**
- * Count of reuse failures which have occurred.
- */
- private volatile int mFailedBitmapReuseCount = 0;
-
- /**
- * Count of reuse successes which have occurred.
- */
- private volatile int mSucceededBitmapReuseCount = 0;
-
- /**
- * A sparse array from bitmap size to a list of image cache entries that match the
- * given size. This map is used to quickly retrieve a usable bitmap to be reused by an
- * incoming ImageRequest. We need to ensure that this sparse array always contains only
- * elements currently in the image cache with no other consumer.
- */
- private final SparseArray<LinkedList<ImageResource>> mImageListSparseArray;
-
- public ReusableImageResourcePool() {
- mImageListSparseArray = new SparseArray<LinkedList<ImageResource>>();
- }
-
- /**
- * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce
- * memory turnover.
- * @param inputStream InputStream load. Cannot be null.
- * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool().
- * Cannot be null.
- * @param width The width of the bitmap.
- * @param height The height of the bitmap.
- * @return The decoded Bitmap with the resource drawn in it.
- * @throws IOException
- */
- public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream,
- @NonNull final BitmapFactory.Options optionsTmp,
- final int width, final int height) throws IOException {
- if (width <= 0 || height <= 0) {
- // This is an invalid / corrupted image of zero size.
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " +
- "invalid size");
- throw new IOException("Invalid size / corrupted image");
- }
- Assert.notNull(inputStream);
- assignPoolBitmap(optionsTmp, width, height);
- Bitmap b = null;
- try {
- b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
- mSucceededBitmapReuseCount++;
- } catch (final IllegalArgumentException e) {
- // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
- if (optionsTmp.inBitmap != null) {
- optionsTmp.inBitmap.recycle();
- optionsTmp.inBitmap = null;
- b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
- onFailedToReuse();
- }
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream");
- Factory.get().reclaimMemory();
- }
- return b;
- }
-
- /**
- * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce
- * memory turnover.
- * @param bytes Encoded bytes to draw on the bitmap. Cannot be null.
- * @param optionsTmp The bitmap will set here and the input should be generated from
- * getBitmapOptionsForPool(). Cannot be null.
- * @param width The width of the bitmap.
- * @param height The height of the bitmap.
- * @return A Bitmap with the encoded bytes drawn in it.
- * @throws IOException
- */
- public Bitmap decodeByteArray(@NonNull final byte[] bytes,
- @NonNull final BitmapFactory.Options optionsTmp, final int width,
- final int height) throws OutOfMemoryError, IOException {
- if (width <= 0 || height <= 0) {
- // This is an invalid / corrupted image of zero size.
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " +
- "invalid size");
- throw new IOException("Invalid size / corrupted image");
- }
- Assert.notNull(bytes);
- Assert.notNull(optionsTmp);
- assignPoolBitmap(optionsTmp, width, height);
- Bitmap b = null;
- try {
- b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
- mSucceededBitmapReuseCount++;
- } catch (final IllegalArgumentException e) {
- // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
- // (i.e. without the bitmap from the pool)
- if (optionsTmp.inBitmap != null) {
- optionsTmp.inBitmap.recycle();
- optionsTmp.inBitmap = null;
- b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
- onFailedToReuse();
- }
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream");
- Factory.get().reclaimMemory();
- }
- return b;
- }
-
- /**
- * Called when a new image resource is added to the cache. We add the resource to the
- * pool so it's properly keyed into the pool structure.
- */
- void onResourceEnterCache(final ImageResource imageResource) {
- if (getPoolKey(imageResource) != INVALID_POOL_KEY) {
- addResourceToPool(imageResource);
- }
- }
-
- /**
- * Called when an image resource is evicted from the cache. Bitmap pool's entries are
- * strictly tied to their presence in the image cache. Once an image is evicted from the
- * cache, it should be removed from the pool.
- */
- void onResourceLeaveCache(final ImageResource imageResource) {
- if (getPoolKey(imageResource) != INVALID_POOL_KEY) {
- removeResourceFromPool(imageResource);
- }
- }
-
- private void addResourceToPool(final ImageResource imageResource) {
- synchronized (PoolableImageCache.this) {
- final int poolKey = getPoolKey(imageResource);
- Assert.isTrue(poolKey != INVALID_POOL_KEY);
- LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey);
- if (imageList == null) {
- imageList = new LinkedList<ImageResource>();
- mImageListSparseArray.put(poolKey, imageList);
- }
- imageList.addLast(imageResource);
- }
- }
-
- private void removeResourceFromPool(final ImageResource imageResource) {
- synchronized (PoolableImageCache.this) {
- final int poolKey = getPoolKey(imageResource);
- Assert.isTrue(poolKey != INVALID_POOL_KEY);
- final LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey);
- if (imageList != null) {
- imageList.remove(imageResource);
- }
- }
- }
-
- /**
- * Try to get a reusable bitmap from the pool with the given width and height. As a
- * result of this call, the caller will assume ownership of the returned bitmap.
- */
- private Bitmap getReusableBitmapFromPool(final int width, final int height) {
- synchronized (PoolableImageCache.this) {
- final int poolKey = getPoolKey(width, height);
- if (poolKey != INVALID_POOL_KEY) {
- final LinkedList<ImageResource> images = mImageListSparseArray.get(poolKey);
- if (images != null && images.size() > 0) {
- // Try to reuse the first available bitmap from the pool list. We start from
- // the least recently added cache entry of the given size.
- ImageResource imageToUse = null;
- for (int i = 0; i < images.size(); i++) {
- final ImageResource image = images.get(i);
- if (image.getRefCount() == 1) {
- image.acquireLock();
- if (image.getRefCount() == 1) {
- // The image is only used by the cache, so it's reusable.
- imageToUse = images.remove(i);
- break;
- } else {
- // Logically, this shouldn't happen, because as soon as the
- // cache is the only user of this resource, it will not be
- // used by anyone else until the next cache access, but we
- // currently hold on to the cache lock. But technically
- // future changes may violate this assumption, so warn about
- // this.
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Image refCount changed " +
- "from 1 in getReusableBitmapFromPool()");
- image.releaseLock();
- }
- }
- }
-
- if (imageToUse == null) {
- return null;
- }
-
- try {
- imageToUse.assertLockHeldByCurrentThread();
-
- // Only reuse the bitmap if the last time we use was greater than 5s.
- // This allows the cache a chance to reuse instead of always taking the
- // oldest.
- final long timeSinceLastRef = SystemClock.elapsedRealtime() -
- imageToUse.getLastRefAddTimestamp();
- if (timeSinceLastRef < MIN_TIME_IN_POOL) {
- if (LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "Not reusing reusing " +
- "first available bitmap from the pool because it " +
- "has not been in the pool long enough. " +
- "timeSinceLastRef=" + timeSinceLastRef);
- }
- // Put back the image and return no reuseable bitmap.
- images.addLast(imageToUse);
- return null;
- }
-
- // Add a temp ref on the image resource so it won't be GC'd after
- // being removed from the cache.
- imageToUse.addRef();
-
- // Remove the image resource from the image cache.
- final ImageResource removed = remove(imageToUse.getKey());
- Assert.isTrue(removed == imageToUse);
-
- // Try to reuse the bitmap from the image resource. This will transfer
- // ownership of the bitmap object to the caller of this method.
- final Bitmap reusableBitmap = imageToUse.reuseBitmap();
-
- imageToUse.release();
- return reusableBitmap;
- } finally {
- // We are either done with the reuse operation, or decided not to use
- // the image. Either way, release the lock.
- imageToUse.releaseLock();
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Try to locate and return a reusable bitmap from the pool, or create a new bitmap.
- * @param width desired bitmap width
- * @param height desired bitmap height
- * @return the created or reused mutable bitmap that has its background cleared to
- * {@value Color#TRANSPARENT}
- */
- public Bitmap createOrReuseBitmap(final int width, final int height) {
- return createOrReuseBitmap(width, height, Color.TRANSPARENT);
- }
-
- /**
- * Try to locate and return a reusable bitmap from the pool, or create a new bitmap.
- * @param width desired bitmap width
- * @param height desired bitmap height
- * @param backgroundColor the background color for the returned bitmap
- * @return the created or reused mutable bitmap with the requested background color
- */
- public Bitmap createOrReuseBitmap(final int width, final int height,
- final int backgroundColor) {
- Bitmap retBitmap = null;
- try {
- final Bitmap poolBitmap = getReusableBitmapFromPool(width, height);
- retBitmap = (poolBitmap != null) ? poolBitmap :
- Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- retBitmap.eraseColor(backgroundColor);
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache:try to createOrReuseBitmap");
- Factory.get().reclaimMemory();
- }
- return retBitmap;
- }
-
- private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width,
- final int height) {
- if (optionsTmp.inJustDecodeBounds) {
- return;
- }
- optionsTmp.inBitmap = getReusableBitmapFromPool(width, height);
- }
-
- /**
- * @return The pool key for the provided image dimensions or 0 if either width or height is
- * greater than the max supported image dimension.
- */
- private int getPoolKey(final int width, final int height) {
- if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) {
- return INVALID_POOL_KEY;
- }
- return (width << 16) | height;
- }
-
- /**
- * @return the pool key for a given image resource.
- */
- private int getPoolKey(final ImageResource imageResource) {
- if (imageResource.supportsBitmapReuse()) {
- final Bitmap bitmap = imageResource.getBitmap();
- if (bitmap != null && bitmap.isMutable()) {
- final int width = bitmap.getWidth();
- final int height = bitmap.getHeight();
- if (width > 0 && height > 0) {
- return getPoolKey(width, height);
- }
- }
- }
- return INVALID_POOL_KEY;
- }
-
- /**
- * Called when bitmap reuse fails. Conditionally report the failure with statistics.
- */
- private void onFailedToReuse() {
- mFailedBitmapReuseCount++;
- if (mFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG,
- "Pooled bitmap consistently not being reused. Failure count = " +
- mFailedBitmapReuseCount + ", success count = " +
- mSucceededBitmapReuseCount);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java b/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java
deleted file mode 100644
index c21f477..0000000
--- a/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.os.SystemClock;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.google.common.base.Throwables;
-
-import java.util.ArrayList;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A ref-counted class that holds loaded media resource, be it bitmaps or media bytes.
- * Subclasses must implement the close() method to release any resources (such as bitmaps)
- * when it's no longer used.
- *
- * Instances of the subclasses are:
- * 1. Loaded by their corresponding MediaRequest classes.
- * 2. Maintained by MediaResourceManager in its MediaCache pool.
- * 3. Used by the UI (such as ContactIconViews) to present the content.
- *
- * Note: all synchronized methods in this class (e.g. addRef()) should not attempt to make outgoing
- * calls that could potentially acquire media cache locks due to the potential deadlock this can
- * cause. To synchronize read/write access to shared resource, {@link #acquireLock()} and
- * {@link #releaseLock()} must be used, instead of using synchronized keyword.
- */
-public abstract class RefCountedMediaResource {
- private final String mKey;
- private int mRef = 0;
- private long mLastRefAddTimestamp;
-
- // Set DEBUG to true to enable detailed stack trace for each addRef() and release() operation
- // to find out where each ref change happens.
- private static final boolean DEBUG = false;
- private static final String TAG = "bugle_media_ref_history";
- private final ArrayList<String> mRefHistory = new ArrayList<String>();
-
- // A lock that guards access to shared members in this class (and all its subclasses).
- private final ReentrantLock mLock = new ReentrantLock();
-
- public RefCountedMediaResource(final String key) {
- mKey = key;
- }
-
- public String getKey() {
- return mKey;
- }
-
- public void addRef() {
- acquireLock();
- try {
- if (DEBUG) {
- mRefHistory.add("Added ref current ref = " + mRef);
- mRefHistory.add(Throwables.getStackTraceAsString(new Exception()));
- }
-
- mRef++;
- mLastRefAddTimestamp = SystemClock.elapsedRealtime();
- } finally {
- releaseLock();
- }
- }
-
- public void release() {
- acquireLock();
- try {
- if (DEBUG) {
- mRefHistory.add("Released ref current ref = " + mRef);
- mRefHistory.add(Throwables.getStackTraceAsString(new Exception()));
- }
-
- mRef--;
- if (mRef == 0) {
- close();
- } else if (mRef < 0) {
- if (DEBUG) {
- LogUtil.i(TAG, "Unwinding ref count history for RefCountedMediaResource "
- + this);
- for (final String ref : mRefHistory) {
- LogUtil.i(TAG, ref);
- }
- }
- Assert.fail("RefCountedMediaResource has unbalanced ref. Refcount=" + mRef);
- }
- } finally {
- releaseLock();
- }
- }
-
- public int getRefCount() {
- acquireLock();
- try {
- return mRef;
- } finally {
- releaseLock();
- }
- }
-
- public long getLastRefAddTimestamp() {
- acquireLock();
- try {
- return mLastRefAddTimestamp;
- } finally {
- releaseLock();
- }
- }
-
- public void assertSingularRefCount() {
- acquireLock();
- try {
- Assert.equals(1, mRef);
- } finally {
- releaseLock();
- }
- }
-
- void acquireLock() {
- mLock.lock();
- }
-
- void releaseLock() {
- mLock.unlock();
- }
-
- void assertLockHeldByCurrentThread() {
- Assert.isTrue(mLock.isHeldByCurrentThread());
- }
-
- boolean isEncoded() {
- return false;
- }
-
- boolean isCacheable() {
- return true;
- }
-
- MediaRequest<? extends RefCountedMediaResource> getMediaDecodingRequest(
- final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
- return null;
- }
-
- MediaRequest<? extends RefCountedMediaResource> getMediaEncodingRequest(
- final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
- return null;
- }
-
- public abstract int getMediaSize();
- protected abstract void close();
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java b/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java
deleted file mode 100644
index e4f0334..0000000
--- a/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.media.ExifInterface;
-import android.text.TextUtils;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-
-import java.io.IOException;
-import java.util.List;
-
-public class SimSelectorAvatarRequest extends AvatarRequest {
- private static Bitmap sRegularSimIcon;
-
- public SimSelectorAvatarRequest(final Context context,
- final AvatarRequestDescriptor descriptor) {
- super(context, descriptor);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTasks)
- throws IOException {
- Assert.isNotMainThread();
- final String avatarType = AvatarUriUtil.getAvatarType(mDescriptor.uri);
- if (AvatarUriUtil.TYPE_SIM_SELECTOR_URI.equals(avatarType)){
- final int width = mDescriptor.desiredWidth;
- final int height = mDescriptor.desiredHeight;
- final String identifier = AvatarUriUtil.getIdentifier(mDescriptor.uri);
- final boolean simSelected = AvatarUriUtil.getSimSelected(mDescriptor.uri);
- final int simColor = AvatarUriUtil.getSimColor(mDescriptor.uri);
- final boolean incoming = AvatarUriUtil.getSimIncoming(mDescriptor.uri);
- return renderSimAvatarInternal(identifier, width, height, simColor, simSelected,
- incoming);
- }
- return super.loadMediaInternal(chainedTasks);
- }
-
- private ImageResource renderSimAvatarInternal(final String identifier, final int width,
- final int height, final int subColor, final boolean selected, final boolean incoming) {
- final Resources resources = mContext.getResources();
- final float halfWidth = width / 2;
- final float halfHeight = height / 2;
- final int minOfWidthAndHeight = Math.min(width, height);
- final int backgroundColor = selected ? subColor : Color.WHITE;
- final int textColor = selected ? subColor : Color.WHITE;
- final int simColor = selected ? Color.WHITE : subColor;
- final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height, backgroundColor);
- final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- final Canvas canvas = new Canvas(bitmap);
-
- if (sRegularSimIcon == null) {
- final BitmapDrawable regularSim = (BitmapDrawable) mContext.getResources()
- .getDrawable(R.drawable.ic_sim_card_send);
- sRegularSimIcon = regularSim.getBitmap();
- }
-
- paint.setColorFilter(new PorterDuffColorFilter(simColor, PorterDuff.Mode.SRC_ATOP));
- paint.setAlpha(0xff);
- canvas.drawBitmap(sRegularSimIcon, halfWidth - sRegularSimIcon.getWidth() / 2,
- halfHeight - sRegularSimIcon.getHeight() / 2, paint);
- paint.setColorFilter(null);
- paint.setAlpha(0xff);
-
- if (!TextUtils.isEmpty(identifier)) {
- paint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
- paint.setColor(textColor);
- final float letterToTileRatio =
- resources.getFraction(R.dimen.sim_identifier_to_tile_ratio, 1, 1);
- paint.setTextSize(letterToTileRatio * minOfWidthAndHeight);
-
- final String firstCharString = identifier.substring(0, 1).toUpperCase();
- final Rect textBound = new Rect();
- paint.getTextBounds(firstCharString, 0, 1, textBound);
-
- final float xOffset = halfWidth - textBound.centerX();
- final float yOffset = halfHeight - textBound.centerY();
- canvas.drawText(firstCharString, xOffset, yOffset, paint);
- }
-
- return new DecodedImageResource(getKey(), bitmap, ExifInterface.ORIENTATION_NORMAL);
- }
-
- @Override
- public int getCacheId() {
- return BugleMediaCacheManager.AVATAR_IMAGE_CACHE;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/UriImageRequest.java b/src/com/android/messaging/datamodel/media/UriImageRequest.java
deleted file mode 100644
index b4934ca..0000000
--- a/src/com/android/messaging/datamodel/media/UriImageRequest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * Serves local content URI based image requests.
- */
-public class UriImageRequest<D extends UriImageRequestDescriptor> extends ImageRequest<D> {
- public UriImageRequest(final Context context, final D descriptor) {
- super(context, descriptor);
- }
-
- @Override
- protected InputStream getInputStreamForResource() throws FileNotFoundException {
- return mContext.getContentResolver().openInputStream(mDescriptor.uri);
- }
-
- @Override
- protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTasks)
- throws IOException {
- final ImageResource resource = super.loadMediaInternal(chainedTasks);
- // Check if the caller asked for compression. If so, chain an encoding task if possible.
- if (mDescriptor.allowCompression && chainedTasks != null) {
- @SuppressWarnings("unchecked")
- final MediaRequest<ImageResource> chainedTask = (MediaRequest<ImageResource>)
- resource.getMediaEncodingRequest(this);
- if (chainedTask != null) {
- chainedTasks.add(chainedTask);
- // Don't cache decoded image resource since we'll perform compression and cache
- // the compressed resource.
- if (resource instanceof DecodedImageResource) {
- ((DecodedImageResource) resource).setCacheable(false);
- }
- }
- }
- return resource;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java
deleted file mode 100644
index c5685d1..0000000
--- a/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.net.Uri;
-
-import com.android.messaging.util.UriUtil;
-
-public class UriImageRequestDescriptor extends ImageRequestDescriptor {
- public final Uri uri;
- public final boolean allowCompression;
-
- public UriImageRequestDescriptor(final Uri uri) {
- this(uri, UriImageRequest.UNSPECIFIED_SIZE, UriImageRequest.UNSPECIFIED_SIZE, false, false,
- false, 0, 0);
- }
-
- public UriImageRequestDescriptor(final Uri uri, final int desiredWidth, final int desiredHeight)
- {
- this(uri, desiredWidth, desiredHeight, false, false, false, 0, 0);
- }
-
- public UriImageRequestDescriptor(final Uri uri, final int desiredWidth, final int desiredHeight,
- final boolean cropToCircle, final int circleBackgroundColor, int circleStrokeColor)
- {
- this(uri, desiredWidth, desiredHeight, false,
- false, cropToCircle, circleBackgroundColor, circleStrokeColor);
- }
-
- public UriImageRequestDescriptor(final Uri uri, final int desiredWidth,
- final int desiredHeight, final boolean allowCompression, boolean isStatic,
- boolean cropToCircle, int circleBackgroundColor, int circleStrokeColor) {
- this(uri, desiredWidth, desiredHeight, UriImageRequest.UNSPECIFIED_SIZE,
- UriImageRequest.UNSPECIFIED_SIZE, allowCompression, isStatic, cropToCircle,
- circleBackgroundColor, circleStrokeColor);
- }
-
- /**
- * Creates a new Uri-based image request.
- * @param uri the content Uri. Currently Bugle only supports local resources Uri (i.e. it has
- * to begin with content: or android.resource:
- * @param circleStrokeColor
- */
- public UriImageRequestDescriptor(final Uri uri, final int desiredWidth,
- final int desiredHeight, final int sourceWidth, final int sourceHeight,
- final boolean allowCompression, final boolean isStatic, final boolean cropToCircle,
- final int circleBackgroundColor, int circleStrokeColor) {
- super(desiredWidth, desiredHeight, sourceWidth, sourceHeight, isStatic,
- cropToCircle, circleBackgroundColor, circleStrokeColor);
- this.uri = uri;
- this.allowCompression = allowCompression;
- }
-
- @Override
- public String getKey() {
- if (uri != null) {
- final String key = super.getKey();
- if (key != null) {
- return new StringBuilder()
- .append(uri).append(KEY_PART_DELIMITER)
- .append(String.valueOf(allowCompression)).append(KEY_PART_DELIMITER)
- .append(key).toString();
- }
- }
- return null;
- }
-
- @Override
- public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
- if (uri == null || UriUtil.isLocalUri(uri)) {
- return new UriImageRequest<UriImageRequestDescriptor>(context, this);
- } else {
- return new NetworkUriImageRequest<UriImageRequestDescriptor>(context, this);
- }
- }
-
- /** ID of the resource in MediaStore or null if this resource didn't come from MediaStore */
- public Long getMediaStoreId() {
- return null;
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/VCardRequest.java b/src/com/android/messaging/datamodel/media/VCardRequest.java
deleted file mode 100644
index d6e992c..0000000
--- a/src/com/android/messaging/datamodel/media/VCardRequest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.UriUtil;
-import com.android.vcard.VCardConfig;
-import com.android.vcard.VCardEntry;
-import com.android.vcard.VCardEntryCounter;
-import com.android.vcard.VCardInterpreter;
-import com.android.vcard.VCardParser;
-import com.android.vcard.VCardParser_V21;
-import com.android.vcard.VCardParser_V30;
-import com.android.vcard.VCardSourceDetector;
-import com.android.vcard.exception.VCardException;
-import com.android.vcard.exception.VCardNestedException;
-import com.android.vcard.exception.VCardNotSupportedException;
-import com.android.vcard.exception.VCardVersionException;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Requests and parses VCard data. In Bugle, we need to display VCard details in the conversation
- * view such as avatar icon and name, which can be expensive if we parse VCard every time.
- * Therefore, we'd like to load the vcard once and cache it in our media cache using the
- * MediaResourceManager component. To load the VCard, we use framework's VCard support to
- * interpret the VCard content, which gives us information such as phone and email list, which
- * we'll put in VCardResource object to be cached.
- *
- * Some particular attention is needed for the avatar icon. If the VCard contains avatar icon,
- * it's in byte array form that can't easily be cached/persisted. Therefore, we persist the
- * image bytes to the scratch directory and generate a content Uri for it, so that ContactIconView
- * may use this Uri to display and cache the image if needed.
- */
-public class VCardRequest implements MediaRequest<VCardResource> {
- private final Context mContext;
- private final VCardRequestDescriptor mDescriptor;
- private final List<VCardResourceEntry> mLoadedVCards;
- private VCardResource mLoadedResource;
- private static final int VCARD_LOADING_TIMEOUT_MILLIS = 10000; // 10s
- private static final String DEFAULT_VCARD_TYPE = "default";
-
- VCardRequest(final Context context, final VCardRequestDescriptor descriptor) {
- mDescriptor = descriptor;
- mContext = context;
- mLoadedVCards = new ArrayList<VCardResourceEntry>();
- }
-
- @Override
- public String getKey() {
- return mDescriptor.vCardUri.toString();
- }
-
- @Override
- @DoesNotRunOnMainThread
- public VCardResource loadMediaBlocking(List<MediaRequest<VCardResource>> chainedTask)
- throws Exception {
- Assert.isNotMainThread();
- Assert.isTrue(mLoadedResource == null);
- Assert.equals(0, mLoadedVCards.size());
-
- // The VCard library doesn't support synchronously loading the media resource. Therefore,
- // We have to burn the thread waiting for the result to come back.
- final CountDownLatch signal = new CountDownLatch(1);
- if (!parseVCard(mDescriptor.vCardUri, signal)) {
- // Directly fail without actually going through the interpreter, return immediately.
- throw new VCardException("Invalid vcard");
- }
-
- signal.await(VCARD_LOADING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- if (mLoadedResource == null) {
- // Maybe null if failed or timeout.
- throw new VCardException("Failure or timeout loading vcard");
- }
- return mLoadedResource;
- }
-
- @Override
- public int getCacheId() {
- return BugleMediaCacheManager.VCARD_CACHE;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public MediaCache<VCardResource> getMediaCache() {
- return (MediaCache<VCardResource>) MediaCacheManager.get().getOrCreateMediaCacheById(
- getCacheId());
- }
-
- @DoesNotRunOnMainThread
- private boolean parseVCard(final Uri targetUri, final CountDownLatch signal) {
- Assert.isNotMainThread();
- final VCardEntryCounter counter = new VCardEntryCounter();
- final VCardSourceDetector detector = new VCardSourceDetector();
- boolean result;
- try {
- // We don't know which type should be used to parse the Uri.
- // It is possible to misinterpret the vCard, but we expect the parser
- // lets VCardSourceDetector detect the type before the misinterpretation.
- result = readOneVCardFile(targetUri, VCardConfig.VCARD_TYPE_UNKNOWN,
- detector, true, null);
- } catch (final VCardNestedException e) {
- try {
- final int estimatedVCardType = detector.getEstimatedType();
- // Assume that VCardSourceDetector was able to detect the source.
- // Try again with the detector.
- result = readOneVCardFile(targetUri, estimatedVCardType,
- counter, false, null);
- } catch (final VCardNestedException e2) {
- result = false;
- LogUtil.e(LogUtil.BUGLE_TAG, "Must not reach here. " + e2);
- }
- }
-
- if (!result) {
- // Load failure.
- return false;
- }
-
- return doActuallyReadOneVCard(targetUri, true, detector, null, signal);
- }
-
- @DoesNotRunOnMainThread
- private boolean doActuallyReadOneVCard(final Uri uri, final boolean showEntryParseProgress,
- final VCardSourceDetector detector, final List<String> errorFileNameList,
- final CountDownLatch signal) {
- Assert.isNotMainThread();
- int vcardType = detector.getEstimatedType();
- if (vcardType == VCardConfig.VCARD_TYPE_UNKNOWN) {
- vcardType = VCardConfig.getVCardTypeFromString(DEFAULT_VCARD_TYPE);
- }
- final CustomVCardEntryConstructor builder =
- new CustomVCardEntryConstructor(vcardType, null);
- builder.addEntryHandler(new ContactVCardEntryHandler(signal));
-
- try {
- if (!readOneVCardFile(uri, vcardType, builder, false, null)) {
- return false;
- }
- } catch (final VCardNestedException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Must not reach here. " + e);
- return false;
- }
- return true;
- }
-
- @DoesNotRunOnMainThread
- private boolean readOneVCardFile(final Uri uri, final int vcardType,
- final VCardInterpreter interpreter,
- final boolean throwNestedException, final List<String> errorFileNameList)
- throws VCardNestedException {
- Assert.isNotMainThread();
- final ContentResolver resolver = mContext.getContentResolver();
- VCardParser vCardParser;
- InputStream is;
- try {
- is = resolver.openInputStream(uri);
- vCardParser = new VCardParser_V21(vcardType);
- vCardParser.addInterpreter(interpreter);
-
- try {
- vCardParser.parse(is);
- } catch (final VCardVersionException e1) {
- try {
- is.close();
- } catch (final IOException e) {
- // Do nothing.
- }
- if (interpreter instanceof CustomVCardEntryConstructor) {
- // Let the object clean up internal temporal objects,
- ((CustomVCardEntryConstructor) interpreter).clear();
- }
-
- is = resolver.openInputStream(uri);
-
- try {
- vCardParser = new VCardParser_V30(vcardType);
- vCardParser.addInterpreter(interpreter);
- vCardParser.parse(is);
- } catch (final VCardVersionException e2) {
- throw new VCardException("vCard with unspported version.");
- }
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- // Do nothing.
- }
- }
- }
- } catch (final IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "IOException was emitted: " + e.getMessage());
-
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- }
- return false;
- } catch (final VCardNotSupportedException e) {
- if ((e instanceof VCardNestedException) && throwNestedException) {
- throw (VCardNestedException) e;
- }
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- }
- return false;
- } catch (final VCardException e) {
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- }
- return false;
- }
- return true;
- }
-
- class ContactVCardEntryHandler implements CustomVCardEntryConstructor.EntryHandler {
- final CountDownLatch mSignal;
-
- public ContactVCardEntryHandler(final CountDownLatch signal) {
- mSignal = signal;
- }
-
- @Override
- public void onStart() {
- }
-
- @Override
- @DoesNotRunOnMainThread
- public void onEntryCreated(final CustomVCardEntry entry) {
- Assert.isNotMainThread();
- final String displayName = entry.getDisplayName();
- final List<VCardEntry.PhotoData> photos = entry.getPhotoList();
- Uri avatarUri = null;
- if (photos != null && photos.size() > 0) {
- // The photo data is in bytes form, so we need to persist it in our temp directory
- // so that ContactIconView can load it and display it later
- // (and cache it, of course).
- for (final VCardEntry.PhotoData photo : photos) {
- final byte[] photoBytes = photo.getBytes();
- if (photoBytes != null) {
- final InputStream inputStream = new ByteArrayInputStream(photoBytes);
- try {
- avatarUri = UriUtil.persistContentToScratchSpace(inputStream);
- if (avatarUri != null) {
- // Just load the first avatar and be done. Want more? wait for V2.
- break;
- }
- } finally {
- try {
- inputStream.close();
- } catch (final IOException e) {
- // Do nothing.
- }
- }
- }
- }
- }
-
- // Fall back to generated avatar.
- if (avatarUri == null) {
- String destination = null;
- final List<VCardEntry.PhoneData> phones = entry.getPhoneList();
- if (phones != null && phones.size() > 0) {
- destination = PhoneUtils.getDefault().getCanonicalBySystemLocale(
- phones.get(0).getNumber());
- }
-
- if (destination == null) {
- final List<VCardEntry.EmailData> emails = entry.getEmailList();
- if (emails != null && emails.size() > 0) {
- destination = emails.get(0).getAddress();
- }
- }
- avatarUri = AvatarUriUtil.createAvatarUri(null, displayName, destination, null);
- }
-
- // Add the loaded vcard to the list.
- mLoadedVCards.add(new VCardResourceEntry(entry, avatarUri));
- }
-
- @Override
- public void onEnd() {
- // Finished loading all vCard entries, signal the loading thread to proceed with the
- // result.
- if (mLoadedVCards.size() > 0) {
- mLoadedResource = new VCardResource(getKey(), mLoadedVCards);
- }
- mSignal.countDown();
- }
- }
-
- @Override
- public int getRequestType() {
- return MediaRequest.REQUEST_LOAD_MEDIA;
- }
-
- @Override
- public MediaRequestDescriptor<VCardResource> getDescriptor() {
- return mDescriptor;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java b/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java
deleted file mode 100644
index 4084851..0000000
--- a/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-import android.net.Uri;
-
-import com.android.messaging.util.Assert;
-
-public class VCardRequestDescriptor extends MediaRequestDescriptor<VCardResource> {
- public final Uri vCardUri;
-
- public VCardRequestDescriptor(final Uri vCardUri) {
- Assert.notNull(vCardUri);
- this.vCardUri = vCardUri;
- }
-
- @Override
- public MediaRequest<VCardResource> buildSyncMediaRequest(Context context) {
- return new VCardRequest(context, this);
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/VCardResource.java b/src/com/android/messaging/datamodel/media/VCardResource.java
deleted file mode 100644
index edf5e88..0000000
--- a/src/com/android/messaging/datamodel/media/VCardResource.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import java.util.List;
-
-/**
- * Holds cached information of VCard contact info.
- * The temporarily persisted avatar icon Uri is tied to the VCardResource. As a result, whenever
- * the VCardResource is no longer used (i.e. close() is called), we need to asynchronously
- * delete the avatar image from temp storage since no one will have reference to the avatar Uri
- * again. The next time the same VCard is displayed, since the old resource has been evicted from
- * the memory cache, we'll load and persist the avatar icon again.
- */
-public class VCardResource extends RefCountedMediaResource {
- private final List<VCardResourceEntry> mVCards;
-
- public VCardResource(final String key, final List<VCardResourceEntry> vcards) {
- super(key);
- mVCards = vcards;
- }
-
- public List<VCardResourceEntry> getVCards() {
- return mVCards;
- }
-
- @Override
- public int getMediaSize() {
- // Instead of track VCards by size in kilobytes, we track them by count.
- return 0;
- }
-
- @Override
- protected void close() {
- for (final VCardResourceEntry vcard : mVCards) {
- vcard.close();
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/VCardResourceEntry.java b/src/com/android/messaging/datamodel/media/VCardResourceEntry.java
deleted file mode 100644
index f76b796..0000000
--- a/src/com/android/messaging/datamodel/media/VCardResourceEntry.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Intent;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.net.Uri;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.support.v4.util.ArrayMap;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.vcard.VCardEntry;
-import com.android.vcard.VCardEntry.EmailData;
-import com.android.vcard.VCardEntry.ImData;
-import com.android.vcard.VCardEntry.NoteData;
-import com.android.vcard.VCardEntry.OrganizationData;
-import com.android.vcard.VCardEntry.PhoneData;
-import com.android.vcard.VCardEntry.PostalData;
-import com.android.vcard.VCardEntry.WebsiteData;
-import com.android.vcard.VCardProperty;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Holds one entry item (i.e. a single contact) within a VCard resource. It is able to take
- * a VCardEntry and extract relevant information from it.
- */
-public class VCardResourceEntry {
- public static final String PROPERTY_KIND = "KIND";
-
- public static final String KIND_LOCATION = "location";
-
- private final List<VCardResourceEntry.VCardResourceEntryDestinationItem> mContactInfo;
- private final Uri mAvatarUri;
- private final String mDisplayName;
- private final CustomVCardEntry mVCard;
-
- public VCardResourceEntry(final CustomVCardEntry vcard, final Uri avatarUri) {
- mContactInfo = getContactInfoFromVCardEntry(vcard);
- mDisplayName = getDisplayNameFromVCardEntry(vcard);
- mAvatarUri = avatarUri;
- mVCard = vcard;
- }
-
- void close() {
- // If the avatar image was temporarily saved in the scratch folder, remove that.
- if (MediaScratchFileProvider.isMediaScratchSpaceUri(mAvatarUri)) {
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- Factory.get().getApplicationContext().getContentResolver().delete(
- mAvatarUri, null, null);
- }
- });
- }
- }
-
- public String getKind() {
- VCardProperty kindProperty = mVCard.getProperty(PROPERTY_KIND);
- return kindProperty == null ? null : kindProperty.getRawValue();
- }
-
- public Uri getAvatarUri() {
- return mAvatarUri;
- }
-
- public String getDisplayName() {
- return mDisplayName;
- }
-
- public String getDisplayAddress() {
- List<PostalData> postalList = mVCard.getPostalList();
- if (postalList == null || postalList.size() < 1) {
- return null;
- }
-
- return formatAddress(postalList.get(0));
- }
-
- public String getNotes() {
- List<NoteData> notes = mVCard.getNotes();
- if (notes == null || notes.size() == 0) {
- return null;
- }
- StringBuilder noteBuilder = new StringBuilder();
- for (NoteData note : notes) {
- noteBuilder.append(note.getNote());
- }
- return noteBuilder.toString();
- }
-
- /**
- * Returns a UI-facing representation that can be bound and consumed by the UI layer to display
- * this VCard resource entry.
- */
- public PersonItemData getDisplayItem() {
- return new PersonItemData() {
- @Override
- public Uri getAvatarUri() {
- return VCardResourceEntry.this.getAvatarUri();
- }
-
- @Override
- public String getDisplayName() {
- return VCardResourceEntry.this.getDisplayName();
- }
-
- @Override
- public String getDetails() {
- return null;
- }
-
- @Override
- public Intent getClickIntent() {
- return null;
- }
-
- @Override
- public long getContactId() {
- return ContactUtil.INVALID_CONTACT_ID;
- }
-
- @Override
- public String getLookupKey() {
- return null;
- }
-
- @Override
- public String getNormalizedDestination() {
- return null;
- }
- };
- }
-
- public List<VCardResourceEntry.VCardResourceEntryDestinationItem> getContactInfo() {
- return mContactInfo;
- }
-
- private static List<VCardResourceEntryDestinationItem> getContactInfoFromVCardEntry(
- final VCardEntry vcard) {
- final Resources resources = Factory.get().getApplicationContext().getResources();
- final List<VCardResourceEntry.VCardResourceEntryDestinationItem> retList =
- new ArrayList<VCardResourceEntry.VCardResourceEntryDestinationItem>();
- if (vcard.getPhoneList() != null) {
- for (final PhoneData phone : vcard.getPhoneList()) {
- final Intent intent = new Intent(Intent.ACTION_DIAL);
- intent.setData(Uri.parse("tel:" + phone.getNumber()));
- retList.add(new VCardResourceEntryDestinationItem(phone.getNumber(),
- Phone.getTypeLabel(resources, phone.getType(), phone.getLabel()).toString(),
- intent));
- }
- }
-
- if (vcard.getEmailList() != null) {
- for (final EmailData email : vcard.getEmailList()) {
- final Intent intent = new Intent(Intent.ACTION_SENDTO);
- intent.setData(Uri.parse("mailto:"));
- intent.putExtra(Intent.EXTRA_EMAIL, new String[] { email.getAddress() });
- retList.add(new VCardResourceEntryDestinationItem(email.getAddress(),
- Phone.getTypeLabel(resources, email.getType(),
- email.getLabel()).toString(), intent));
- }
- }
-
- if (vcard.getPostalList() != null) {
- for (final PostalData postalData : vcard.getPostalList()) {
- String type;
- try {
- type = resources.
- getStringArray(android.R.array.postalAddressTypes)
- [postalData.getType() - 1];
- } catch (final NotFoundException ex) {
- type = resources.getStringArray(android.R.array.postalAddressTypes)[2];
- } catch (final Exception e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "createContactItem postal Exception:" + e);
- type = resources.getStringArray(android.R.array.postalAddressTypes)[2];
- }
- Intent intent = new Intent(Intent.ACTION_VIEW);
- final String address = formatAddress(postalData);
- try {
- intent.setData(Uri.parse("geo:0,0?q=" + URLEncoder.encode(address, "UTF-8")));
- } catch (UnsupportedEncodingException e) {
- intent = null;
- }
-
- retList.add(new VCardResourceEntryDestinationItem(address, type, intent));
- }
- }
-
- if (vcard.getImList() != null) {
- for (final ImData imData : vcard.getImList()) {
- String type = null;
- try {
- type = resources.
- getString(Im.getProtocolLabelResource(imData.getProtocol()));
- } catch (final NotFoundException ex) {
- // Do nothing since this implies an empty label.
- }
- retList.add(new VCardResourceEntryDestinationItem(imData.getAddress(), type, null));
- }
- }
-
- if (vcard.getOrganizationList() != null) {
- for (final OrganizationData organtization : vcard.getOrganizationList()) {
- String type = null;
- try {
- type = resources.getString(Organization.getTypeLabelResource(
- organtization.getType()));
- } catch (final NotFoundException ex) {
- //set other kind as "other"
- type = resources.getStringArray(android.R.array.organizationTypes)[1];
- } catch (final Exception e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "createContactItem org Exception:" + e);
- type = resources.getStringArray(android.R.array.organizationTypes)[1];
- }
- retList.add(new VCardResourceEntryDestinationItem(
- organtization.getOrganizationName(), type, null));
- }
- }
-
- if (vcard.getWebsiteList() != null) {
- for (final WebsiteData web : vcard.getWebsiteList()) {
- if (web != null && TextUtils.isGraphic(web.getWebsite())){
- String website = web.getWebsite();
- if (!website.startsWith("http://") && !website.startsWith("https://")) {
- // Prefix required for parsing to end up with a scheme and result in
- // navigation
- website = "http://" + website;
- }
- final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(website));
- retList.add(new VCardResourceEntryDestinationItem(web.getWebsite(), null,
- intent));
- }
- }
- }
-
- if (vcard.getBirthday() != null) {
- final String birthday = vcard.getBirthday();
- if (TextUtils.isGraphic(birthday)){
- retList.add(new VCardResourceEntryDestinationItem(birthday,
- resources.getString(R.string.vcard_detail_birthday_label), null));
- }
- }
-
- if (vcard.getNotes() != null) {
- for (final NoteData note : vcard.getNotes()) {
- final ArrayMap<String, String> curChildMap = new ArrayMap<String, String>();
- if (TextUtils.isGraphic(note.getNote())){
- retList.add(new VCardResourceEntryDestinationItem(note.getNote(),
- resources.getString(R.string.vcard_detail_notes_label), null));
- }
- }
- }
- return retList;
- }
-
- private static String formatAddress(final PostalData postalData) {
- final StringBuilder sb = new StringBuilder();
- final String poBox = postalData.getPobox();
- if (!TextUtils.isEmpty(poBox)) {
- sb.append(poBox);
- sb.append(" ");
- }
- final String extendedAddress = postalData.getExtendedAddress();
- if (!TextUtils.isEmpty(extendedAddress)) {
- sb.append(extendedAddress);
- sb.append(" ");
- }
- final String street = postalData.getStreet();
- if (!TextUtils.isEmpty(street)) {
- sb.append(street);
- sb.append(" ");
- }
- final String localty = postalData.getLocalty();
- if (!TextUtils.isEmpty(localty)) {
- sb.append(localty);
- sb.append(" ");
- }
- final String region = postalData.getRegion();
- if (!TextUtils.isEmpty(region)) {
- sb.append(region);
- sb.append(" ");
- }
- final String postalCode = postalData.getPostalCode();
- if (!TextUtils.isEmpty(postalCode)) {
- sb.append(postalCode);
- sb.append(" ");
- }
- final String country = postalData.getCountry();
- if (!TextUtils.isEmpty(country)) {
- sb.append(country);
- }
- return sb.toString();
- }
-
- private static String getDisplayNameFromVCardEntry(final VCardEntry vcard) {
- String name = vcard.getDisplayName();
- if (name == null) {
- vcard.consolidateFields();
- name = vcard.getDisplayName();
- }
- return name;
- }
-
- /**
- * Represents one entry line (e.g. phone number and phone label) for a single contact. Each
- * VCardResourceEntry may hold one or more VCardResourceEntryDestinationItem's.
- */
- public static class VCardResourceEntryDestinationItem {
- private final String mDisplayDestination;
- private final String mDestinationType;
- private final Intent mClickIntent;
- public VCardResourceEntryDestinationItem(final String displayDestination,
- final String destinationType, final Intent clickIntent) {
- mDisplayDestination = displayDestination;
- mDestinationType = destinationType;
- mClickIntent = clickIntent;
- }
-
- /**
- * Returns a UI-facing representation that can be bound and consumed by the UI layer to
- * display this VCard resource destination entry.
- */
- public PersonItemData getDisplayItem() {
- return new PersonItemData() {
- @Override
- public Uri getAvatarUri() {
- return null;
- }
-
- @Override
- public String getDisplayName() {
- return mDisplayDestination;
- }
-
- @Override
- public String getDetails() {
- return mDestinationType;
- }
-
- @Override
- public Intent getClickIntent() {
- return mClickIntent;
- }
-
- @Override
- public long getContactId() {
- return ContactUtil.INVALID_CONTACT_ID;
- }
-
- @Override
- public String getLookupKey() {
- return null;
- }
-
- @Override
- public String getNormalizedDestination() {
- return null;
- }
- };
- }
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
deleted file mode 100644
index f17591c..0000000
--- a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.datamodel.media;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.provider.MediaStore.Video.Thumbnails;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.MediaMetadataRetrieverWrapper;
-import com.android.messaging.util.OsUtil;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Class to request a video thumbnail.
- * Users of this class as responsible for checking {@link #shouldShowIncomingVideoThumbnails}
- */
-public class VideoThumbnailRequest extends ImageRequest<UriImageRequestDescriptor> {
-
- public VideoThumbnailRequest(final Context context,
- final UriImageRequestDescriptor descriptor) {
- super(context, descriptor);
- }
-
- public static boolean shouldShowIncomingVideoThumbnails() {
- return OsUtil.isAtLeastM();
- }
-
- @Override
- protected InputStream getInputStreamForResource() throws FileNotFoundException {
- return null;
- }
-
- @Override
- protected boolean hasBitmapObject() {
- return true;
- }
-
- @Override
- protected Bitmap getBitmapForResource() throws IOException {
- final Long mediaId = mDescriptor.getMediaStoreId();
- Bitmap bitmap = null;
- if (mediaId != null) {
- final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
- bitmap = Thumbnails.getThumbnail(cr, mediaId, Thumbnails.MICRO_KIND, null);
- } else {
- final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
- try {
- retriever.setDataSource(mDescriptor.uri);
- bitmap = retriever.getFrameAtTime();
- } finally {
- retriever.release();
- }
- }
- if (bitmap != null) {
- mDescriptor.updateSourceDimensions(bitmap.getWidth(), bitmap.getHeight());
- }
- return bitmap;
- }
-}
diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java
deleted file mode 100644
index 907bb8f..0000000
--- a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.content.Context;
-
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.UriUtil;
-
-public class VideoThumbnailRequestDescriptor extends UriImageRequestDescriptor {
- protected final long mMediaId;
- public VideoThumbnailRequestDescriptor(final long id, String path, int desiredWidth,
- int desiredHeight, int sourceWidth, int sourceHeight) {
- super(UriUtil.getUriForResourceFile(path), desiredWidth, desiredHeight, sourceWidth,
- sourceHeight, false /* canCompress */, false /* isStatic */,
- false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- mMediaId = id;
- }
-
- @Override
- public MediaRequest<ImageResource> buildSyncMediaRequest(Context context) {
- return new VideoThumbnailRequest(context, this);
- }
-
- @Override
- public Long getMediaStoreId() {
- return mMediaId;
- }
-}
diff --git a/src/com/android/messaging/mmslib/Downloads.java b/src/com/android/messaging/mmslib/Downloads.java
deleted file mode 100644
index 9afc48c..0000000
--- a/src/com/android/messaging/mmslib/Downloads.java
+++ /dev/null
@@ -1,807 +0,0 @@
-/*
- * Copyright (C) 2008 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.messaging.mmslib;
-
-import android.app.DownloadManager;
-import android.content.Context;
-import android.net.Uri;
-import android.provider.BaseColumns;
-
-/**
- * The Download Manager
- *
- * @pending
- */
-public final class Downloads {
- private Downloads() {}
-
- /**
- * Implementation details
- *
- * Exposes constants used to interact with the download manager's
- * content provider.
- * The constants URI ... STATUS are the names of columns in the downloads table.
- *
- * @hide
- */
- public static final class Impl implements BaseColumns {
- private Impl() {}
-
- /**
- * The permission to access the download manager
- */
- public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
-
- /**
- * The permission to access the download manager's advanced functions
- */
- public static final String PERMISSION_ACCESS_ADVANCED =
- "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
-
- /**
- * The permission to access the all the downloads in the manager.
- */
- public static final String PERMISSION_ACCESS_ALL =
- "android.permission.ACCESS_ALL_DOWNLOADS";
-
- /**
- * The permission to directly access the download manager's cache
- * directory
- */
- public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
-
- /**
- * The permission to send broadcasts on download completion
- */
- public static final String PERMISSION_SEND_INTENTS =
- "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
-
- /**
- * The permission to download files to the cache partition that won't be automatically
- * purged when space is needed.
- */
- public static final String PERMISSION_CACHE_NON_PURGEABLE =
- "android.permission.DOWNLOAD_CACHE_NON_PURGEABLE";
-
- /**
- * The permission to download files without any system notification being shown.
- */
- public static final String PERMISSION_NO_NOTIFICATION =
- "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION";
-
- /**
- * The content:// URI to access downloads owned by the caller's UID.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://downloads/my_downloads");
-
- /**
- * The content URI for accessing all downloads across all UIDs (requires the
- * ACCESS_ALL_DOWNLOADS permission).
- */
- public static final Uri ALL_DOWNLOADS_CONTENT_URI =
- Uri.parse("content://downloads/all_downloads");
-
- /** URI segment to access a publicly accessible downloaded file */
- public static final String PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT = "public_downloads";
-
- /**
- * The content URI for accessing publicly accessible downloads (i.e., it requires no
- * permissions to access this downloaded file)
- */
- public static final Uri PUBLICLY_ACCESSIBLE_DOWNLOADS_URI =
- Uri.parse("content://downloads/" + PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT);
-
- /**
- * Broadcast Action: this is sent by the download manager to the app
- * that had initiated a download when that download completes. The
- * download's content: uri is specified in the intent's data.
- */
- public static final String ACTION_DOWNLOAD_COMPLETED =
- "android.intent.action.DOWNLOAD_COMPLETED";
-
- /**
- * Broadcast Action: this is sent by the download manager to the app
- * that had initiated a download when the user selects the notification
- * associated with that download. The download's content: uri is specified
- * in the intent's data if the click is associated with a single download,
- * or Downloads.CONTENT_URI if the notification is associated with
- * multiple downloads.
- * Note: this is not currently sent for downloads that have completed
- * successfully.
- */
- public static final String ACTION_NOTIFICATION_CLICKED =
- "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
-
- /**
- * The name of the column containing the URI of the data being downloaded.
- * <P>Type: TEXT</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_URI = "uri";
-
- /**
- * The name of the column containing application-specific data.
- * <P>Type: TEXT</P>
- * <P>Owner can Init/Read/Write</P>
- */
- public static final String COLUMN_APP_DATA = "entity";
-
- /**
- * The name of the column containing the flags that indicates whether
- * the initiating application is capable of verifying the integrity of
- * the downloaded file. When this flag is set, the download manager
- * performs downloads and reports success even in some situations where
- * it can't guarantee that the download has completed (e.g. when doing
- * a byte-range request without an ETag, or when it can't determine
- * whether a download fully completed).
- * <P>Type: BOOLEAN</P>
- * <P>Owner can Init</P>
- */
- public static final String COLUMN_NO_INTEGRITY = "no_integrity";
-
- /**
- * The name of the column containing the filename that the initiating
- * application recommends. When possible, the download manager will attempt
- * to use this filename, or a variation, as the actual name for the file.
- * <P>Type: TEXT</P>
- * <P>Owner can Init</P>
- */
- public static final String COLUMN_FILE_NAME_HINT = "hint";
-
- /**
- * The name of the column containing the filename where the downloaded data
- * was actually stored.
- * <P>Type: TEXT</P>
- * <P>Owner can Read</P>
- */
- public static final String _DATA = "_data";
-
- /**
- * The name of the column containing the MIME type of the downloaded data.
- * <P>Type: TEXT</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_MIME_TYPE = "mimetype";
-
- /**
- * The name of the column containing the flag that controls the destination
- * of the download. See the DESTINATION_* constants for a list of legal values.
- * <P>Type: INTEGER</P>
- * <P>Owner can Init</P>
- */
- public static final String COLUMN_DESTINATION = "destination";
-
- /**
- * The name of the column containing the flags that controls whether the
- * download is displayed by the UI. See the VISIBILITY_* constants for
- * a list of legal values.
- * <P>Type: INTEGER</P>
- * <P>Owner can Init/Read/Write</P>
- */
- public static final String COLUMN_VISIBILITY = "visibility";
-
- /**
- * The name of the column containing the current control state of the download.
- * Applications can write to this to control (pause/resume) the download.
- * the CONTROL_* constants for a list of legal values.
- * <P>Type: INTEGER</P>
- * <P>Owner can Read</P>
- */
- public static final String COLUMN_CONTROL = "control";
-
- /**
- * The name of the column containing the current status of the download.
- * Applications can read this to follow the progress of each download. See
- * the STATUS_* constants for a list of legal values.
- * <P>Type: INTEGER</P>
- * <P>Owner can Read</P>
- */
- public static final String COLUMN_STATUS = "status";
-
- /**
- * The name of the column containing the date at which some interesting
- * status changed in the download. Stored as a System.currentTimeMillis()
- * value.
- * <P>Type: BIGINT</P>
- * <P>Owner can Read</P>
- */
- public static final String COLUMN_LAST_MODIFICATION = "lastmod";
-
- /**
- * The name of the column containing the package name of the application
- * that initiating the download. The download manager will send
- * notifications to a component in this package when the download completes.
- * <P>Type: TEXT</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
-
- /**
- * The name of the column containing the component name of the class that
- * will receive notifications associated with the download. The
- * package/class combination is passed to
- * Intent.setClassName(String,String).
- * <P>Type: TEXT</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
-
- /**
- * If extras are specified when requesting a download they will be provided in the intent
- * that is sent to the specified class and package when a download has finished.
- * <P>Type: TEXT</P>
- * <P>Owner can Init</P>
- */
- public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
-
- /**
- * The name of the column contain the values of the cookie to be used for
- * the download. This is used directly as the value for the Cookie: HTTP
- * header that gets sent with the request.
- * <P>Type: TEXT</P>
- * <P>Owner can Init</P>
- */
- public static final String COLUMN_COOKIE_DATA = "cookiedata";
-
- /**
- * The name of the column containing the user agent that the initiating
- * application wants the download manager to use for this download.
- * <P>Type: TEXT</P>
- * <P>Owner can Init</P>
- */
- public static final String COLUMN_USER_AGENT = "useragent";
-
- /**
- * The name of the column containing the referer (sic) that the initiating
- * application wants the download manager to use for this download.
- * <P>Type: TEXT</P>
- * <P>Owner can Init</P>
- */
- public static final String COLUMN_REFERER = "referer";
-
- /**
- * The name of the column containing the total size of the file being
- * downloaded.
- * <P>Type: INTEGER</P>
- * <P>Owner can Read</P>
- */
- public static final String COLUMN_TOTAL_BYTES = "total_bytes";
-
- /**
- * The name of the column containing the size of the part of the file that
- * has been downloaded so far.
- * <P>Type: INTEGER</P>
- * <P>Owner can Read</P>
- */
- public static final String COLUMN_CURRENT_BYTES = "current_bytes";
-
- /**
- * The name of the column where the initiating application can provide the
- * UID of another application that is allowed to access this download. If
- * multiple applications share the same UID, all those applications will be
- * allowed to access this download. This column can be updated after the
- * download is initiated. This requires the permission
- * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED.
- * <P>Type: INTEGER</P>
- * <P>Owner can Init</P>
- */
- public static final String COLUMN_OTHER_UID = "otheruid";
-
- /**
- * The name of the column where the initiating application can provided the
- * title of this download. The title will be displayed ito the user in the
- * list of downloads.
- * <P>Type: TEXT</P>
- * <P>Owner can Init/Read/Write</P>
- */
- public static final String COLUMN_TITLE = "title";
-
- /**
- * The name of the column where the initiating application can provide the
- * description of this download. The description will be displayed to the
- * user in the list of downloads.
- * <P>Type: TEXT</P>
- * <P>Owner can Init/Read/Write</P>
- */
- public static final String COLUMN_DESCRIPTION = "description";
-
- /**
- * The name of the column indicating whether the download was requesting through the public
- * API. This controls some differences in behavior.
- * <P>Type: BOOLEAN</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_IS_PUBLIC_API = "is_public_api";
-
- /**
- * The name of the column holding a bitmask of allowed network types. This is only used for
- * public API downloads.
- * <P>Type: INTEGER</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types";
-
- /**
- * The name of the column indicating whether roaming connections can be used. This is only
- * used for public API downloads.
- * <P>Type: BOOLEAN</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_ALLOW_ROAMING = "allow_roaming";
-
- /**
- * The name of the column indicating whether metered connections can be used. This is only
- * used for public API downloads.
- * <P>Type: BOOLEAN</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_ALLOW_METERED = "allow_metered";
-
- /**
- * Whether or not this download should be displayed in the system's Downloads UI. Defaults
- * to true.
- * <P>Type: INTEGER</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui";
-
- /**
- * If true, the user has confirmed that this download can proceed over the mobile network
- * even though it exceeds the recommended maximum size.
- * <P>Type: BOOLEAN</P>
- */
- public static final String COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT =
- "bypass_recommended_size_limit";
-
- /**
- * Set to true if this download is deleted. It is completely removed from the database
- * when MediaProvider database also deletes the metadata asociated with this downloaded
- * file.
- * <P>Type: BOOLEAN</P>
- * <P>Owner can Read</P>
- */
- public static final String COLUMN_DELETED = "deleted";
-
- /**
- * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
- * used to delete the entries from MediaProvider database when it is deleted from the
- * downloaded list.
- * <P>Type: TEXT</P>
- * <P>Owner can Read</P>
- */
- public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
-
- /**
- * The column that is used to remember whether the media scanner was invoked.
- * It can take the values: null or 0(not scanned), 1(scanned), 2 (not scannable).
- * <P>Type: TEXT</P>
- */
- public static final String COLUMN_MEDIA_SCANNED = "scanned";
-
- /**
- * The column with errorMsg for a failed downloaded.
- * Used only for debugging purposes.
- * <P>Type: TEXT</P>
- */
- public static final String COLUMN_ERROR_MSG = "errorMsg";
-
- /**
- * This column stores the source of the last update to this row.
- * This column is only for internal use.
- * Valid values are indicated by LAST_UPDATESRC_* constants.
- * <P>Type: INT</P>
- */
- public static final String COLUMN_LAST_UPDATESRC = "lastUpdateSrc";
-
- /** The column that is used to count retries */
- public static final String COLUMN_FAILED_CONNECTIONS = "numfailed";
-
- /**
- * default value for {@link #COLUMN_LAST_UPDATESRC}.
- * This value is used when this column's value is not relevant.
- */
- public static final int LAST_UPDATESRC_NOT_RELEVANT = 0;
-
- /**
- * One of the values taken by {@link #COLUMN_LAST_UPDATESRC}.
- * This value is used when the update is NOT to be relayed to the DownloadService
- * (and thus spare DownloadService from scanning the database when this change occurs)
- */
- public static final int LAST_UPDATESRC_DONT_NOTIFY_DOWNLOADSVC = 1;
-
- /*
- * Lists the destinations that an application can specify for a download.
- */
-
- /**
- * This download will be saved to the external storage. This is the
- * default behavior, and should be used for any file that the user
- * can freely access, copy, delete. Even with that destination,
- * unencrypted DRM files are saved in secure internal storage.
- * Downloads to the external destination only write files for which
- * there is a registered handler. The resulting files are accessible
- * by filename to all applications.
- */
- public static final int DESTINATION_EXTERNAL = 0;
-
- /**
- * This download will be saved to the download manager's private
- * partition. This is the behavior used by applications that want to
- * download private files that are used and deleted soon after they
- * get downloaded. All file types are allowed, and only the initiating
- * application can access the file (indirectly through a content
- * provider). This requires the
- * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
- */
- public static final int DESTINATION_CACHE_PARTITION = 1;
-
- /**
- * This download will be saved to the download manager's private
- * partition and will be purged as necessary to make space. This is
- * for private files (similar to CACHE_PARTITION) that aren't deleted
- * immediately after they are used, and are kept around by the download
- * manager as long as space is available.
- */
- public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
-
- /**
- * This download will be saved to the download manager's private
- * partition, as with DESTINATION_CACHE_PARTITION, but the download
- * will not proceed if the user is on a roaming data connection.
- */
- public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
-
- /**
- * This download will be saved to the location given by the file URI in
- * {@link #COLUMN_FILE_NAME_HINT}.
- */
- public static final int DESTINATION_FILE_URI = 4;
-
- /**
- * This download will be saved to the system cache ("/cache")
- * partition. This option is only used by system apps and so it requires
- * android.permission.ACCESS_CACHE_FILESYSTEM permission.
- */
- public static final int DESTINATION_SYSTEMCACHE_PARTITION = 5;
-
- /**
- * This download was completed by the caller (i.e., NOT downloadmanager)
- * and caller wants to have this download displayed in Downloads App.
- */
- public static final int DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD = 6;
-
- /**
- * This download is allowed to run.
- */
- public static final int CONTROL_RUN = 0;
-
- /**
- * This download must pause at the first opportunity.
- */
- public static final int CONTROL_PAUSED = 1;
-
- /*
- * Lists the states that the download manager can set on a download
- * to notify applications of the download progress.
- * The codes follow the HTTP families:<br>
- * 1xx: informational<br>
- * 2xx: success<br>
- * 3xx: redirects (not used by the download manager)<br>
- * 4xx: client errors<br>
- * 5xx: server errors
- */
-
- /**
- * Returns whether the status is informational (i.e. 1xx).
- */
- public static boolean isStatusInformational(int status) {
- return (status >= 100 && status < 200);
- }
-
- /**
- * Returns whether the status is a success (i.e. 2xx).
- */
- public static boolean isStatusSuccess(int status) {
- return (status >= 200 && status < 300);
- }
-
- /**
- * Returns whether the status is an error (i.e. 4xx or 5xx).
- */
- public static boolean isStatusError(int status) {
- return (status >= 400 && status < 600);
- }
-
- /**
- * Returns whether the status is a client error (i.e. 4xx).
- */
- public static boolean isStatusClientError(int status) {
- return (status >= 400 && status < 500);
- }
-
- /**
- * Returns whether the status is a server error (i.e. 5xx).
- */
- public static boolean isStatusServerError(int status) {
- return (status >= 500 && status < 600);
- }
-
- /**
- * this method determines if a notification should be displayed for a
- * given {@link #COLUMN_VISIBILITY} value
- * @param visibility the value of {@link #COLUMN_VISIBILITY}.
- * @return true if the notification should be displayed. false otherwise.
- */
- public static boolean isNotificationToBeDisplayed(int visibility) {
- return visibility == DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED ||
- visibility == DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION;
- }
-
- /**
- * Returns whether the download has completed (either with success or
- * error).
- */
- public static boolean isStatusCompleted(int status) {
- return (status >= 200 && status < 300) || (status >= 400 && status < 600);
- }
-
- /**
- * This download hasn't stated yet
- */
- public static final int STATUS_PENDING = 190;
-
- /**
- * This download has started
- */
- public static final int STATUS_RUNNING = 192;
-
- /**
- * This download has been paused by the owning app.
- */
- public static final int STATUS_PAUSED_BY_APP = 193;
-
- /**
- * This download encountered some network error and is waiting before retrying the request.
- */
- public static final int STATUS_WAITING_TO_RETRY = 194;
-
- /**
- * This download is waiting for network connectivity to proceed.
- */
- public static final int STATUS_WAITING_FOR_NETWORK = 195;
-
- /**
- * This download exceeded a size limit for mobile networks and is waiting for a Wi-Fi
- * connection to proceed.
- */
- public static final int STATUS_QUEUED_FOR_WIFI = 196;
-
- /**
- * This download couldn't be completed due to insufficient storage
- * space. Typically, this is because the SD card is full.
- */
- public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 198;
-
- /**
- * This download couldn't be completed because no external storage
- * device was found. Typically, this is because the SD card is not
- * mounted.
- */
- public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 199;
-
- /**
- * This download has successfully completed.
- * Warning: there might be other status values that indicate success
- * in the future.
- * Use isSucccess() to capture the entire category.
- */
- public static final int STATUS_SUCCESS = 200;
-
- /**
- * This request couldn't be parsed. This is also used when processing
- * requests with unknown/unsupported URI schemes.
- */
- public static final int STATUS_BAD_REQUEST = 400;
-
- /**
- * This download can't be performed because the content type cannot be
- * handled.
- */
- public static final int STATUS_NOT_ACCEPTABLE = 406;
-
- /**
- * This download cannot be performed because the length cannot be
- * determined accurately. This is the code for the HTTP error "Length
- * Required", which is typically used when making requests that require
- * a content length but don't have one, and it is also used in the
- * client when a response is received whose length cannot be determined
- * accurately (therefore making it impossible to know when a download
- * completes).
- */
- public static final int STATUS_LENGTH_REQUIRED = 411;
-
- /**
- * This download was interrupted and cannot be resumed.
- * This is the code for the HTTP error "Precondition Failed", and it is
- * also used in situations where the client doesn't have an ETag at all.
- */
- public static final int STATUS_PRECONDITION_FAILED = 412;
-
- /**
- * The lowest-valued error status that is not an actual HTTP status code.
- */
- public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488;
-
- /**
- * The requested destination file already exists.
- */
- public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;
-
- /**
- * Some possibly transient error occurred, but we can't resume the download.
- */
- public static final int STATUS_CANNOT_RESUME = 489;
-
- /**
- * This download was canceled
- */
- public static final int STATUS_CANCELED = 490;
-
- /**
- * This download has completed with an error.
- * Warning: there will be other status values that indicate errors in
- * the future. Use isStatusError() to capture the entire category.
- */
- public static final int STATUS_UNKNOWN_ERROR = 491;
-
- /**
- * This download couldn't be completed because of a storage issue.
- * Typically, that's because the filesystem is missing or full.
- * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
- * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
- */
- public static final int STATUS_FILE_ERROR = 492;
-
- /**
- * This download couldn't be completed because of an HTTP
- * redirect response that the download manager couldn't
- * handle.
- */
- public static final int STATUS_UNHANDLED_REDIRECT = 493;
-
- /**
- * This download couldn't be completed because of an
- * unspecified unhandled HTTP code.
- */
- public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
-
- /**
- * This download couldn't be completed because of an
- * error receiving or processing data at the HTTP level.
- */
- public static final int STATUS_HTTP_DATA_ERROR = 495;
-
- /**
- * This download couldn't be completed because of an
- * HttpException while setting up the request.
- */
- public static final int STATUS_HTTP_EXCEPTION = 496;
-
- /**
- * This download couldn't be completed because there were
- * too many redirects.
- */
- public static final int STATUS_TOO_MANY_REDIRECTS = 497;
-
- /**
- * This download has failed because requesting application has been
- * blocked by {@link NetworkPolicyManager}.
- *
- * @hide
- * @deprecated since behavior now uses
- * {@link #STATUS_WAITING_FOR_NETWORK}
- */
- @Deprecated
- public static final int STATUS_BLOCKED = 498;
-
- /** {@hide} */
- public static String statusToString(int status) {
- switch (status) {
- case STATUS_PENDING: return "PENDING";
- case STATUS_RUNNING: return "RUNNING";
- case STATUS_PAUSED_BY_APP: return "PAUSED_BY_APP";
- case STATUS_WAITING_TO_RETRY: return "WAITING_TO_RETRY";
- case STATUS_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK";
- case STATUS_QUEUED_FOR_WIFI: return "QUEUED_FOR_WIFI";
- case STATUS_INSUFFICIENT_SPACE_ERROR: return "INSUFFICIENT_SPACE_ERROR";
- case STATUS_DEVICE_NOT_FOUND_ERROR: return "DEVICE_NOT_FOUND_ERROR";
- case STATUS_SUCCESS: return "SUCCESS";
- case STATUS_BAD_REQUEST: return "BAD_REQUEST";
- case STATUS_NOT_ACCEPTABLE: return "NOT_ACCEPTABLE";
- case STATUS_LENGTH_REQUIRED: return "LENGTH_REQUIRED";
- case STATUS_PRECONDITION_FAILED: return "PRECONDITION_FAILED";
- case STATUS_FILE_ALREADY_EXISTS_ERROR: return "FILE_ALREADY_EXISTS_ERROR";
- case STATUS_CANNOT_RESUME: return "CANNOT_RESUME";
- case STATUS_CANCELED: return "CANCELED";
- case STATUS_UNKNOWN_ERROR: return "UNKNOWN_ERROR";
- case STATUS_FILE_ERROR: return "FILE_ERROR";
- case STATUS_UNHANDLED_REDIRECT: return "UNHANDLED_REDIRECT";
- case STATUS_UNHANDLED_HTTP_CODE: return "UNHANDLED_HTTP_CODE";
- case STATUS_HTTP_DATA_ERROR: return "HTTP_DATA_ERROR";
- case STATUS_HTTP_EXCEPTION: return "HTTP_EXCEPTION";
- case STATUS_TOO_MANY_REDIRECTS: return "TOO_MANY_REDIRECTS";
- case STATUS_BLOCKED: return "BLOCKED";
- default: return Integer.toString(status);
- }
- }
-
- /**
- * This download is visible but only shows in the notifications
- * while it's in progress.
- */
- public static final int VISIBILITY_VISIBLE = DownloadManager.Request.VISIBILITY_VISIBLE;
-
- /**
- * This download is visible and shows in the notifications while
- * in progress and after completion.
- */
- public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED =
- DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
-
- /**
- * This download doesn't show in the UI or in the notifications.
- */
- public static final int VISIBILITY_HIDDEN = DownloadManager.Request.VISIBILITY_HIDDEN;
-
- /**
- * Constants related to HTTP request headers associated with each download.
- */
- public static class RequestHeaders {
- public static final String HEADERS_DB_TABLE = "request_headers";
- public static final String COLUMN_DOWNLOAD_ID = "download_id";
- public static final String COLUMN_HEADER = "header";
- public static final String COLUMN_VALUE = "value";
-
- /**
- * Path segment to add to a download URI to retrieve request headers
- */
- public static final String URI_SEGMENT = "headers";
-
- /**
- * Prefix for ContentValues keys that contain HTTP header lines, to be passed to
- * DownloadProvider.insert().
- */
- public static final String INSERT_KEY_PREFIX = "http_header_";
- }
- }
-
- /**
- * Query where clause for general querying.
- */
- private static final String QUERY_WHERE_CLAUSE = Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND "
- + Impl.COLUMN_NOTIFICATION_CLASS + "=?";
-
- /**
- * Delete all the downloads for a package/class pair.
- */
- public static final void removeAllDownloadsByPackage(
- Context context, String notificationPackage, String notificationClass) {
- context.getContentResolver().delete(Impl.CONTENT_URI, QUERY_WHERE_CLAUSE,
- new String[] { notificationPackage, notificationClass });
- }
-}
diff --git a/src/com/android/messaging/mmslib/InvalidHeaderValueException.java b/src/com/android/messaging/mmslib/InvalidHeaderValueException.java
deleted file mode 100644
index c141591..0000000
--- a/src/com/android/messaging/mmslib/InvalidHeaderValueException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib;
-
-/**
- * Thrown when an invalid header value was set.
- */
-public class InvalidHeaderValueException extends MmsException {
- private static final long serialVersionUID = -2053384496042052262L;
-
- /**
- * Constructs an InvalidHeaderValueException with no detailed message.
- */
- public InvalidHeaderValueException() {
- super();
- }
-
- /**
- * Constructs an InvalidHeaderValueException with the specified detailed message.
- *
- * @param message the detailed message.
- */
- public InvalidHeaderValueException(String message) {
- super(message);
- }
-}
diff --git a/src/com/android/messaging/mmslib/MmsException.java b/src/com/android/messaging/mmslib/MmsException.java
deleted file mode 100644
index 173511d..0000000
--- a/src/com/android/messaging/mmslib/MmsException.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib;
-
-/**
- * A generic exception that is thrown by the Mms client.
- */
-public class MmsException extends Exception {
- private static final long serialVersionUID = -7323249827281485390L;
-
- /**
- * Creates a new MmsException.
- */
- public MmsException() {
- super();
- }
-
- /**
- * Creates a new MmsException with the specified detail message.
- *
- * @param message the detail message.
- */
- public MmsException(String message) {
- super(message);
- }
-
- /**
- * Creates a new MmsException with the specified cause.
- *
- * @param cause the cause.
- */
- public MmsException(Throwable cause) {
- super(cause);
- }
-
- /**
- * Creates a new MmsException with the specified detail message and cause.
- *
- * @param message the detail message.
- * @param cause the cause.
- */
- public MmsException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/com/android/messaging/mmslib/SqliteWrapper.java b/src/com/android/messaging/mmslib/SqliteWrapper.java
deleted file mode 100644
index 8ef0e6f..0000000
--- a/src/com/android/messaging/mmslib/SqliteWrapper.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008 Esmertec AG.
- * Copyright (C) 2008 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.messaging.mmslib;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-
-import com.android.messaging.util.LogUtil;
-
-// Wrapper around content resolver methods to catch exceptions
-public final class SqliteWrapper {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private SqliteWrapper() {
- // Forbidden being instantiated.
- }
-
- public static Cursor query(Context context, ContentResolver resolver, Uri uri,
- String[] projection, String selection, String[] selectionArgs, String sortOrder) {
- try {
- return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
- } catch (SQLiteException e) {
- LogUtil.e(TAG, "SqliteWrapper: catch an exception when query", e);
- return null;
- } catch (IllegalArgumentException e) {
- LogUtil.e(TAG, "SqliteWrapper: catch an exception when query", e);
- return null;
- }
- }
-
- public static int update(Context context, ContentResolver resolver, Uri uri,
- ContentValues values, String where, String[] selectionArgs) {
- try {
- return resolver.update(uri, values, where, selectionArgs);
- } catch (SQLiteException e) {
- LogUtil.e(TAG, "SqliteWrapper: catch an exception when update", e);
- return -1;
- } catch (IllegalArgumentException e) {
- LogUtil.e(TAG, "SqliteWrapper: catch an exception when update", e);
- return -1;
- }
- }
-
- public static int delete(Context context, ContentResolver resolver, Uri uri,
- String where, String[] selectionArgs) {
- try {
- return resolver.delete(uri, where, selectionArgs);
- } catch (SQLiteException e) {
- LogUtil.e(TAG, "SqliteWrapper: catch an exception when delete", e);
- return -1;
- } catch (IllegalArgumentException e) {
- LogUtil.e(TAG, "SqliteWrapper: catch an exception when delete", e);
- return -1;
- }
- }
-
- public static Uri insert(Context context, ContentResolver resolver,
- Uri uri, ContentValues values) {
- try {
- return resolver.insert(uri, values);
- } catch (SQLiteException e) {
- LogUtil.e(TAG, "SqliteWrapper: catch an exception when insert", e);
- return null;
- } catch (IllegalArgumentException e) {
- LogUtil.e(TAG, "SqliteWrapper: catch an exception when insert", e);
- return null;
- }
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java b/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java
deleted file mode 100644
index 05494c0..0000000
--- a/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-/**
- * M-Acknowledge.ind PDU.
- */
-public class AcknowledgeInd extends GenericPdu {
- /**
- * Constructor, used when composing a M-Acknowledge.ind pdu.
- *
- * @param mmsVersion current viersion of mms
- * @param transactionId the transaction-id value
- * @throws InvalidHeaderValueException if parameters are invalid.
- * @throws NullPointerException if transactionId is null.
- */
- public AcknowledgeInd(int mmsVersion, byte[] transactionId)
- throws InvalidHeaderValueException {
- super();
-
- setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
- setMmsVersion(mmsVersion);
- setTransactionId(transactionId);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- AcknowledgeInd(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Get X-Mms-Report-Allowed field value.
- *
- * @return the X-Mms-Report-Allowed value
- */
- public int getReportAllowed() {
- return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
- }
-
- /**
- * Set X-Mms-Report-Allowed field value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setReportAllowed(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
- }
-
- /**
- * Get X-Mms-Transaction-Id field value.
- *
- * @return the X-Mms-Report-Allowed value
- */
- public byte[] getTransactionId() {
- return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
- }
-
- /**
- * Set X-Mms-Transaction-Id field value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setTransactionId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/Base64.java b/src/com/android/messaging/mmslib/pdu/Base64.java
deleted file mode 100644
index 2f27117..0000000
--- a/src/com/android/messaging/mmslib/pdu/Base64.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-public class Base64 {
- /**
- * Used to get the number of Quadruples.
- */
- static final int FOURBYTE = 4;
-
- /**
- * Byte used to pad output.
- */
- static final byte PAD = (byte) '=';
-
- /**
- * The base length.
- */
- static final int BASELENGTH = 255;
-
- // Create arrays to hold the base64 characters
- private static byte[] base64Alphabet = new byte[BASELENGTH];
-
- // Populating the character arrays
- static {
- for (int i = 0; i < BASELENGTH; i++) {
- base64Alphabet[i] = (byte) -1;
- }
- for (int i = 'Z'; i >= 'A'; i--) {
- base64Alphabet[i] = (byte) (i - 'A');
- }
- for (int i = 'z'; i >= 'a'; i--) {
- base64Alphabet[i] = (byte) (i - 'a' + 26);
- }
- for (int i = '9'; i >= '0'; i--) {
- base64Alphabet[i] = (byte) (i - '0' + 52);
- }
-
- base64Alphabet['+'] = 62;
- base64Alphabet['/'] = 63;
- }
-
- /**
- * Decodes Base64 data into octects
- *
- * @param base64Data Byte array containing Base64 data
- * @return Array containing decoded data.
- */
- public static byte[] decodeBase64(byte[] base64Data) {
- // RFC 2045 requires that we discard ALL non-Base64 characters
- base64Data = discardNonBase64(base64Data);
-
- // handle the edge case, so we don't have to worry about it later
- if (base64Data.length == 0) {
- return new byte[0];
- }
-
- int numberQuadruple = base64Data.length / FOURBYTE;
- byte decodedData[] = null;
- byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
-
- // Throw away anything not in base64Data
-
- int encodedIndex = 0;
- int dataIndex = 0;
- {
- // this sizes the output array properly - rlw
- int lastData = base64Data.length;
- // ignore the '=' padding
- while (base64Data[lastData - 1] == PAD) {
- if (--lastData == 0) {
- return new byte[0];
- }
- }
- decodedData = new byte[lastData - numberQuadruple];
- }
-
- for (int i = 0; i < numberQuadruple; i++) {
- dataIndex = i * 4;
- marker0 = base64Data[dataIndex + 2];
- marker1 = base64Data[dataIndex + 3];
-
- b1 = base64Alphabet[base64Data[dataIndex]];
- b2 = base64Alphabet[base64Data[dataIndex + 1]];
-
- if (marker0 != PAD && marker1 != PAD) {
- //No PAD e.g 3cQl
- b3 = base64Alphabet[marker0];
- b4 = base64Alphabet[marker1];
-
- decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
- decodedData[encodedIndex + 1] =
- (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
- decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
- } else if (marker0 == PAD) {
- //Two PAD e.g. 3c[Pad][Pad]
- decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
- } else if (marker1 == PAD) {
- //One PAD e.g. 3cQ[Pad]
- b3 = base64Alphabet[marker0];
-
- decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
- decodedData[encodedIndex + 1] =
- (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
- }
- encodedIndex += 3;
- }
- return decodedData;
- }
-
- /**
- * Check octect wheter it is a base64 encoding.
- *
- * @param octect to be checked byte
- * @return ture if it is base64 encoding, false otherwise.
- */
- private static boolean isBase64(byte octect) {
- if (octect == PAD) {
- return true;
- } else if (base64Alphabet[octect] == -1) {
- return false;
- } else {
- return true;
- }
- }
-
- /**
- * Discards any characters outside of the base64 alphabet, per
- * the requirements on page 25 of RFC 2045 - "Any characters
- * outside of the base64 alphabet are to be ignored in base64
- * encoded data."
- *
- * @param data The base-64 encoded data to groom
- * @return The data, less non-base64 characters (see RFC 2045).
- */
- static byte[] discardNonBase64(byte[] data) {
- byte groomedData[] = new byte[data.length];
- int bytesCopied = 0;
-
- for (int i = 0; i < data.length; i++) {
- if (isBase64(data[i])) {
- groomedData[bytesCopied++] = data[i];
- }
- }
-
- byte packedData[] = new byte[bytesCopied];
-
- System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
-
- return packedData;
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/CharacterSets.java b/src/com/android/messaging/mmslib/pdu/CharacterSets.java
deleted file mode 100644
index 0a9099b..0000000
--- a/src/com/android/messaging/mmslib/pdu/CharacterSets.java
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import android.support.v4.util.SimpleArrayMap;
-import android.util.SparseArray;
-
-import java.io.UnsupportedEncodingException;
-
-public class CharacterSets {
- /**
- * IANA assigned MIB enum numbers.
- *
- * From wap-230-wsp-20010705-a.pdf
- * Any-charset = <Octet 128>
- * Equivalent to the special RFC2616 charset value "*"
- *
- * Link to all the charsets: http://www.iana.org/assignments/character-sets/character-sets.xhtml
- * Use Charset.availableCharsets() to see if a potential charset is supported in java:
- * private void dumpCharsets() {
- * logvv("dumpCharsets");
- * SortedMap<String, Charset> charsets = Charset.availableCharsets();
- * for (Entry<String, Charset> entry : charsets.entrySet()) {
- * String key = entry.getKey();
- * Charset value = entry.getValue();
- * logvv("charset key: " + key + " value: " + value);
- * }
- * }
- *
- * As of March 21, 2014, here is the list from dumpCharsets:
- * Adobe-Standard-Encoding value: java.nio.charset.CharsetICU[Adobe-Standard-Encoding]
- * Big5 value: java.nio.charset.CharsetICU[Big5]
- * Big5-HKSCS value: java.nio.charset.CharsetICU[Big5-HKSCS]
- * BOCU-1 value: java.nio.charset.CharsetICU[BOCU-1]
- * CESU-8 value: java.nio.charset.CharsetICU[CESU-8]
- * cp1363 value: java.nio.charset.CharsetICU[cp1363]
- * cp851 value: java.nio.charset.CharsetICU[cp851]
- * cp864 value: java.nio.charset.CharsetICU[cp864]
- * EUC-JP value: java.nio.charset.CharsetICU[EUC-JP]
- * EUC-KR value: java.nio.charset.CharsetICU[EUC-KR]
- * GB18030 value: java.nio.charset.CharsetICU[GB18030]
- * GBK value: java.nio.charset.CharsetICU[GBK]
- * hp-roman8 value: java.nio.charset.CharsetICU[hp-roman8]
- * HZ-GB-2312 value: java.nio.charset.CharsetICU[HZ-GB-2312]
- * IBM-Thai value: java.nio.charset.CharsetICU[IBM-Thai]
- * IBM00858 value: java.nio.charset.CharsetICU[IBM00858]
- * IBM01140 value: java.nio.charset.CharsetICU[IBM01140]
- * IBM01141 value: java.nio.charset.CharsetICU[IBM01141]
- * IBM01142 value: java.nio.charset.CharsetICU[IBM01142]
- * IBM01143 value: java.nio.charset.CharsetICU[IBM01143]
- * IBM01144 value: java.nio.charset.CharsetICU[IBM01144]
- * IBM01145 value: java.nio.charset.CharsetICU[IBM01145]
- * IBM01146 value: java.nio.charset.CharsetICU[IBM01146]
- * IBM01147 value: java.nio.charset.CharsetICU[IBM01147]
- * IBM01148 value: java.nio.charset.CharsetICU[IBM01148]
- * IBM01149 value: java.nio.charset.CharsetICU[IBM01149]
- * IBM037 value: java.nio.charset.CharsetICU[IBM037]
- * IBM1026 value: java.nio.charset.CharsetICU[IBM1026]
- * IBM1047 value: java.nio.charset.CharsetICU[IBM1047]
- * IBM273 value: java.nio.charset.CharsetICU[IBM273]
- * IBM277 value: java.nio.charset.CharsetICU[IBM277]
- * IBM278 value: java.nio.charset.CharsetICU[IBM278]
- * IBM280 value: java.nio.charset.CharsetICU[IBM280]
- * IBM284 value: java.nio.charset.CharsetICU[IBM284]
- * IBM285 value: java.nio.charset.CharsetICU[IBM285]
- * IBM290 value: java.nio.charset.CharsetICU[IBM290]
- * IBM297 value: java.nio.charset.CharsetICU[IBM297]
- * IBM420 value: java.nio.charset.CharsetICU[IBM420]
- * IBM424 value: java.nio.charset.CharsetICU[IBM424]
- * IBM437 value: java.nio.charset.CharsetICU[IBM437]
- * IBM500 value: java.nio.charset.CharsetICU[IBM500]
- * IBM775 value: java.nio.charset.CharsetICU[IBM775]
- * IBM850 value: java.nio.charset.CharsetICU[IBM850]
- * IBM852 value: java.nio.charset.CharsetICU[IBM852]
- * IBM855 value: java.nio.charset.CharsetICU[IBM855]
- * IBM857 value: java.nio.charset.CharsetICU[IBM857]
- * IBM860 value: java.nio.charset.CharsetICU[IBM860]
- * IBM861 value: java.nio.charset.CharsetICU[IBM861]
- * IBM862 value: java.nio.charset.CharsetICU[IBM862]
- * IBM863 value: java.nio.charset.CharsetICU[IBM863]
- * IBM865 value: java.nio.charset.CharsetICU[IBM865]
- * IBM866 value: java.nio.charset.CharsetICU[IBM866]
- * IBM868 value: java.nio.charset.CharsetICU[IBM868]
- * IBM869 value: java.nio.charset.CharsetICU[IBM869]
- * IBM870 value: java.nio.charset.CharsetICU[IBM870]
- * IBM871 value: java.nio.charset.CharsetICU[IBM871]
- * IBM918 value: java.nio.charset.CharsetICU[IBM918]
- * ISO-2022-CN value: java.nio.charset.CharsetICU[ISO-2022-CN]
- * ISO-2022-CN-EXT value: java.nio.charset.CharsetICU[ISO-2022-CN-EXT]
- * ISO-2022-JP value: java.nio.charset.CharsetICU[ISO-2022-JP]
- * ISO-2022-JP-1 value: java.nio.charset.CharsetICU[ISO-2022-JP-1]
- * ISO-2022-JP-2 value: java.nio.charset.CharsetICU[ISO-2022-JP-2]
- * ISO-2022-KR value: java.nio.charset.CharsetICU[ISO-2022-KR]
- * ISO-8859-1 value: java.nio.charset.CharsetICU[ISO-8859-1]
- * ISO-8859-10 value: java.nio.charset.CharsetICU[ISO-8859-10]
- * ISO-8859-13 value: java.nio.charset.CharsetICU[ISO-8859-13]
- * ISO-8859-14 value: java.nio.charset.CharsetICU[ISO-8859-14]
- * ISO-8859-15 value: java.nio.charset.CharsetICU[ISO-8859-15]
- * ISO-8859-2 value: java.nio.charset.CharsetICU[ISO-8859-2]
- * ISO-8859-3 value: java.nio.charset.CharsetICU[ISO-8859-3]
- * ISO-8859-4 value: java.nio.charset.CharsetICU[ISO-8859-4]
- * ISO-8859-5 value: java.nio.charset.CharsetICU[ISO-8859-5]
- * ISO-8859-6 value: java.nio.charset.CharsetICU[ISO-8859-6]
- * ISO-8859-7 value: java.nio.charset.CharsetICU[ISO-8859-7]
- * ISO-8859-8 value: java.nio.charset.CharsetICU[ISO-8859-8]
- * ISO-8859-9 value: java.nio.charset.CharsetICU[ISO-8859-9]
- * KOI8-R value: java.nio.charset.CharsetICU[KOI8-R]
- * KOI8-U value: java.nio.charset.CharsetICU[KOI8-U]
- * macintosh value: java.nio.charset.CharsetICU[macintosh]
- * SCSU value: java.nio.charset.CharsetICU[SCSU]
- * Shift_JIS value: java.nio.charset.CharsetICU[Shift_JIS]
- * TIS-620 value: java.nio.charset.CharsetICU[TIS-620]
- * US-ASCII value: java.nio.charset.CharsetICU[US-ASCII]
- * UTF-16 value: java.nio.charset.CharsetICU[UTF-16]
- * UTF-16BE value: java.nio.charset.CharsetICU[UTF-16BE]
- * UTF-16LE value: java.nio.charset.CharsetICU[UTF-16LE]
- * UTF-32 value: java.nio.charset.CharsetICU[UTF-32]
- * UTF-32BE value: java.nio.charset.CharsetICU[UTF-32BE]
- * UTF-32LE value: java.nio.charset.CharsetICU[UTF-32LE]
- * UTF-7 value: java.nio.charset.CharsetICU[UTF-7]
- * UTF-8 value: java.nio.charset.CharsetICU[UTF-8]
- * windows-1250 value: java.nio.charset.CharsetICU[windows-1250]
- * windows-1251 value: java.nio.charset.CharsetICU[windows-1251]
- * windows-1252 value: java.nio.charset.CharsetICU[windows-1252]
- * windows-1253 value: java.nio.charset.CharsetICU[windows-1253]
- * windows-1254 value: java.nio.charset.CharsetICU[windows-1254]
- * windows-1255 value: java.nio.charset.CharsetICU[windows-1255]
- * windows-1256 value: java.nio.charset.CharsetICU[windows-1256]
- * windows-1257 value: java.nio.charset.CharsetICU[windows-1257]
- * windows-1258 value: java.nio.charset.CharsetICU[windows-1258]
- * x-compound-text value: java.nio.charset.CharsetICU[x-compound-text]
- * x-ebcdic-xml-us value: java.nio.charset.CharsetICU[x-ebcdic-xml-us]
- * x-gsm-03.38-2000 value: java.nio.charset.CharsetICU[x-gsm-03.38-2000]
- * x-ibm-1047-s390 value: java.nio.charset.CharsetICU[x-ibm-1047-s390]
- * x-ibm-1125_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1125_P100-1997]
- * x-ibm-1129_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1129_P100-1997]
- * x-ibm-1130_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1130_P100-1997]
- * x-ibm-1131_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1131_P100-1997]
- * x-ibm-1132_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-1132_P100-1998]
- * x-ibm-1133_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1133_P100-1997]
- * x-ibm-1137_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1137_P100-1999]
- * x-ibm-1140-s390 value: java.nio.charset.CharsetICU[x-ibm-1140-s390]
- * x-ibm-1141-s390 value: java.nio.charset.CharsetICU[x-ibm-1141-s390]
- * x-ibm-1142-s390 value: java.nio.charset.CharsetICU[x-ibm-1142-s390]
- * x-ibm-1143-s390 value: java.nio.charset.CharsetICU[x-ibm-1143-s390]
- * x-ibm-1144-s390 value: java.nio.charset.CharsetICU[x-ibm-1144-s390]
- * x-ibm-1145-s390 value: java.nio.charset.CharsetICU[x-ibm-1145-s390]
- * x-ibm-1146-s390 value: java.nio.charset.CharsetICU[x-ibm-1146-s390]
- * x-ibm-1147-s390 value: java.nio.charset.CharsetICU[x-ibm-1147-s390]
- * x-ibm-1148-s390 value: java.nio.charset.CharsetICU[x-ibm-1148-s390]
- * x-ibm-1149-s390 value: java.nio.charset.CharsetICU[x-ibm-1149-s390]
- * x-ibm-1153-s390 value: java.nio.charset.CharsetICU[x-ibm-1153-s390]
- * x-ibm-1154_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1154_P100-1999]
- * x-ibm-1155_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1155_P100-1999]
- * x-ibm-1156_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1156_P100-1999]
- * x-ibm-1157_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1157_P100-1999]
- * x-ibm-1158_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1158_P100-1999]
- * x-ibm-1160_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1160_P100-1999]
- * x-ibm-1162_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1162_P100-1999]
- * x-ibm-1164_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1164_P100-1999]
- * x-ibm-1250_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1250_P100-1995]
- * x-ibm-1251_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1251_P100-1995]
- * x-ibm-1252_P100-2000 value: java.nio.charset.CharsetICU[x-ibm-1252_P100-2000]
- * x-ibm-1253_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1253_P100-1995]
- * x-ibm-1254_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1254_P100-1995]
- * x-ibm-1255_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1255_P100-1995]
- * x-ibm-1256_P110-1997 value: java.nio.charset.CharsetICU[x-ibm-1256_P110-1997]
- * x-ibm-1257_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1257_P100-1995]
- * x-ibm-1258_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1258_P100-1997]
- * x-ibm-12712-s390 value: java.nio.charset.CharsetICU[x-ibm-12712-s390]
- * x-ibm-12712_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-12712_P100-1998]
- * x-ibm-1373_P100-2002 value: java.nio.charset.CharsetICU[x-ibm-1373_P100-2002]
- * x-ibm-1383_P110-1999 value: java.nio.charset.CharsetICU[x-ibm-1383_P110-1999]
- * x-ibm-1386_P100-2001 value: java.nio.charset.CharsetICU[x-ibm-1386_P100-2001]
- * x-ibm-16684_P110-2003 value: java.nio.charset.CharsetICU[x-ibm-16684_P110-2003]
- * x-ibm-16804-s390 value: java.nio.charset.CharsetICU[x-ibm-16804-s390]
- * x-ibm-16804_X110-1999 value: java.nio.charset.CharsetICU[x-ibm-16804_X110-1999]
- * x-ibm-25546 value: java.nio.charset.CharsetICU[x-ibm-25546]
- * x-ibm-33722_P12A_P12A-2009_U2 value:
- * java.nio.charset.CharsetICU[x-ibm-33722_P12A_P12A-2009_U2]
- * x-ibm-37-s390 value: java.nio.charset.CharsetICU[x-ibm-37-s390]
- * x-ibm-4517_P100-2005 value: java.nio.charset.CharsetICU[x-ibm-4517_P100-2005]
- * x-ibm-4899_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-4899_P100-1998]
- * x-ibm-4909_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-4909_P100-1999]
- * x-ibm-4971_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-4971_P100-1999]
- * x-ibm-5123_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-5123_P100-1999]
- * x-ibm-5351_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5351_P100-1998]
- * x-ibm-5352_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5352_P100-1998]
- * x-ibm-5353_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5353_P100-1998]
- * x-ibm-5478_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-5478_P100-1995]
- * x-ibm-803_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-803_P100-1999]
- * x-ibm-813_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-813_P100-1995]
- * x-ibm-8482_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-8482_P100-1999]
- * x-ibm-901_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-901_P100-1999]
- * x-ibm-902_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-902_P100-1999]
- * x-ibm-9067_X100-2005 value: java.nio.charset.CharsetICU[x-ibm-9067_X100-2005]
- * x-ibm-916_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-916_P100-1995]
- * x-IBM1006 value: java.nio.charset.CharsetICU[x-IBM1006]
- * x-IBM1025 value: java.nio.charset.CharsetICU[x-IBM1025]
- * x-IBM1097 value: java.nio.charset.CharsetICU[x-IBM1097]
- * x-IBM1098 value: java.nio.charset.CharsetICU[x-IBM1098]
- * x-IBM1112 value: java.nio.charset.CharsetICU[x-IBM1112]
- * x-IBM1122 value: java.nio.charset.CharsetICU[x-IBM1122]
- * x-IBM1123 value: java.nio.charset.CharsetICU[x-IBM1123]
- * x-IBM1124 value: java.nio.charset.CharsetICU[x-IBM1124]
- * x-IBM1153 value: java.nio.charset.CharsetICU[x-IBM1153]
- * x-IBM1363 value: java.nio.charset.CharsetICU[x-IBM1363]
- * x-IBM1364 value: java.nio.charset.CharsetICU[x-IBM1364]
- * x-IBM1371 value: java.nio.charset.CharsetICU[x-IBM1371]
- * x-IBM1388 value: java.nio.charset.CharsetICU[x-IBM1388]
- * x-IBM1390 value: java.nio.charset.CharsetICU[x-IBM1390]
- * x-IBM1399 value: java.nio.charset.CharsetICU[x-IBM1399]
- * x-IBM33722 value: java.nio.charset.CharsetICU[x-IBM33722]
- * x-IBM720 value: java.nio.charset.CharsetICU[x-IBM720]
- * x-IBM737 value: java.nio.charset.CharsetICU[x-IBM737]
- * x-IBM856 value: java.nio.charset.CharsetICU[x-IBM856]
- * x-IBM867 value: java.nio.charset.CharsetICU[x-IBM867]
- * x-IBM875 value: java.nio.charset.CharsetICU[x-IBM875]
- * x-IBM922 value: java.nio.charset.CharsetICU[x-IBM922]
- * x-IBM930 value: java.nio.charset.CharsetICU[x-IBM930]
- * x-IBM933 value: java.nio.charset.CharsetICU[x-IBM933]
- * x-IBM935 value: java.nio.charset.CharsetICU[x-IBM935]
- * x-IBM937 value: java.nio.charset.CharsetICU[x-IBM937]
- * x-IBM939 value: java.nio.charset.CharsetICU[x-IBM939]
- * x-IBM942 value: java.nio.charset.CharsetICU[x-IBM942]
- * x-IBM943 value: java.nio.charset.CharsetICU[x-IBM943]
- * x-IBM949 value: java.nio.charset.CharsetICU[x-IBM949]
- * x-IBM949C value: java.nio.charset.CharsetICU[x-IBM949C]
- * x-IBM950 value: java.nio.charset.CharsetICU[x-IBM950]
- * x-IBM954 value: java.nio.charset.CharsetICU[x-IBM954]
- * x-IBM964 value: java.nio.charset.CharsetICU[x-IBM964]
- * x-IBM970 value: java.nio.charset.CharsetICU[x-IBM970]
- * x-IBM971 value: java.nio.charset.CharsetICU[x-IBM971]
- * x-IMAP-mailbox-name value: java.nio.charset.CharsetICU[x-IMAP-mailbox-name]
- * x-iscii-be value: java.nio.charset.CharsetICU[x-iscii-be]
- * x-iscii-gu value: java.nio.charset.CharsetICU[x-iscii-gu]
- * x-iscii-ka value: java.nio.charset.CharsetICU[x-iscii-ka]
- * x-iscii-ma value: java.nio.charset.CharsetICU[x-iscii-ma]
- * x-iscii-or value: java.nio.charset.CharsetICU[x-iscii-or]
- * x-iscii-pa value: java.nio.charset.CharsetICU[x-iscii-pa]
- * x-iscii-ta value: java.nio.charset.CharsetICU[x-iscii-ta]
- * x-iscii-te value: java.nio.charset.CharsetICU[x-iscii-te]
- * x-ISCII91 value: java.nio.charset.CharsetICU[x-ISCII91]
- * x-ISO-2022-CN-CNS value: java.nio.charset.CharsetICU[x-ISO-2022-CN-CNS]
- * x-iso-8859-11 value: java.nio.charset.CharsetICU[x-iso-8859-11]
- * x-JavaUnicode value: java.nio.charset.CharsetICU[x-JavaUnicode]
- * x-JavaUnicode2 value: java.nio.charset.CharsetICU[x-JavaUnicode2]
- * x-JIS7 value: java.nio.charset.CharsetICU[x-JIS7]
- * x-JIS8 value: java.nio.charset.CharsetICU[x-JIS8]
- * x-LMBCS-1 value: java.nio.charset.CharsetICU[x-LMBCS-1]
- * x-mac-centraleurroman value: java.nio.charset.CharsetICU[x-mac-centraleurroman]
- * x-mac-cyrillic value: java.nio.charset.CharsetICU[x-mac-cyrillic]
- * x-mac-greek value: java.nio.charset.CharsetICU[x-mac-greek]
- * x-mac-turkish value: java.nio.charset.CharsetICU[x-mac-turkish]
- * x-MS950-HKSCS value: java.nio.charset.CharsetICU[x-MS950-HKSCS]
- * x-UnicodeBig value: java.nio.charset.CharsetICU[x-UnicodeBig]
- * x-UTF-16LE-BOM value: java.nio.charset.CharsetICU[x-UTF-16LE-BOM]
- * x-UTF16_OppositeEndian value: java.nio.charset.CharsetICU[x-UTF16_OppositeEndian]
- * x-UTF16_PlatformEndian value: java.nio.charset.CharsetICU[x-UTF16_PlatformEndian]
- * x-UTF32_OppositeEndian value: java.nio.charset.CharsetICU[x-UTF32_OppositeEndian]
- * x-UTF32_PlatformEndian value: java.nio.charset.CharsetICU[x-UTF32_PlatformEndian]
- *
- */
- public static final int ANY_CHARSET = 0x00;
- public static final int US_ASCII = 0x03;
- public static final int ISO_8859_1 = 0x04;
- public static final int ISO_8859_2 = 0x05;
- public static final int ISO_8859_3 = 0x06;
- public static final int ISO_8859_4 = 0x07;
- public static final int ISO_8859_5 = 0x08;
- public static final int ISO_8859_6 = 0x09;
- public static final int ISO_8859_7 = 0x0A;
- public static final int ISO_8859_8 = 0x0B;
- public static final int ISO_8859_9 = 0x0C;
- public static final int SHIFT_JIS = 0x11;
- public static final int EUC_JP = 0x12;
- public static final int EUC_KR = 0x26;
- public static final int ISO_2022_JP = 0x27;
- public static final int ISO_2022_JP_2 = 0x28;
- public static final int UTF_8 = 0x6A;
- public static final int GBK = 0x71;
- public static final int GB18030 = 0x72;
- public static final int GB2312 = 0x07E9;
- public static final int BIG5 = 0x07EA;
- public static final int UCS2 = 0x03E8;
- public static final int UTF_16 = 0x03F7;
- public static final int HZ_GB_2312 = 0x0825;
-
- /**
- * If the encoding of given data is unsupported, use UTF_8 to decode it.
- */
- public static final int DEFAULT_CHARSET = UTF_8;
-
- /**
- * Array of MIB enum numbers.
- */
- private static final int[] MIBENUM_NUMBERS = {
- ANY_CHARSET,
- US_ASCII,
- ISO_8859_1,
- ISO_8859_2,
- ISO_8859_3,
- ISO_8859_4,
- ISO_8859_5,
- ISO_8859_6,
- ISO_8859_7,
- ISO_8859_8,
- ISO_8859_9,
- SHIFT_JIS,
- EUC_JP,
- EUC_KR,
- ISO_2022_JP,
- ISO_2022_JP_2,
- UTF_8,
- GBK,
- GB18030,
- GB2312,
- BIG5,
- UCS2,
- UTF_16,
- HZ_GB_2312,
- };
-
- /**
- * The Well-known-charset Mime name.
- */
- public static final String MIMENAME_ANY_CHARSET = "*";
- public static final String MIMENAME_US_ASCII = "us-ascii";
- public static final String MIMENAME_ISO_8859_1 = "iso-8859-1";
- public static final String MIMENAME_ISO_8859_2 = "iso-8859-2";
- public static final String MIMENAME_ISO_8859_3 = "iso-8859-3";
- public static final String MIMENAME_ISO_8859_4 = "iso-8859-4";
- public static final String MIMENAME_ISO_8859_5 = "iso-8859-5";
- public static final String MIMENAME_ISO_8859_6 = "iso-8859-6";
- public static final String MIMENAME_ISO_8859_7 = "iso-8859-7";
- public static final String MIMENAME_ISO_8859_8 = "iso-8859-8";
- public static final String MIMENAME_ISO_8859_9 = "iso-8859-9";
- public static final String MIMENAME_SHIFT_JIS = "shift_JIS";
- public static final String MIMENAME_EUC_JP = "euc-jp";
- public static final String MIMENAME_EUC_KR = "euc-kr";
- public static final String MIMENAME_ISO_2022_JP = "iso-2022-jp";
- public static final String MIMENAME_ISO_2022_JP_2 = "iso-2022-jp-2";
- public static final String MIMENAME_UTF_8 = "utf-8";
- public static final String MIMENAME_GBK = "gbk";
- public static final String MIMENAME_GB18030 = "gb18030";
- public static final String MIMENAME_GB2312 = "gb2312";
- public static final String MIMENAME_BIG5 = "big5";
- public static final String MIMENAME_UCS2 = "iso-10646-ucs-2";
- public static final String MIMENAME_UTF_16 = "utf-16";
- public static final String MIMENAME_HZ_GB_2312 = "hz-gb-2312";
-
- public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
-
- /**
- * Array of the names of character sets.
- */
- private static final String[] MIME_NAMES = {
- MIMENAME_ANY_CHARSET,
- MIMENAME_US_ASCII,
- MIMENAME_ISO_8859_1,
- MIMENAME_ISO_8859_2,
- MIMENAME_ISO_8859_3,
- MIMENAME_ISO_8859_4,
- MIMENAME_ISO_8859_5,
- MIMENAME_ISO_8859_6,
- MIMENAME_ISO_8859_7,
- MIMENAME_ISO_8859_8,
- MIMENAME_ISO_8859_9,
- MIMENAME_SHIFT_JIS,
- MIMENAME_EUC_JP,
- MIMENAME_EUC_KR,
- MIMENAME_ISO_2022_JP,
- MIMENAME_ISO_2022_JP_2,
- MIMENAME_UTF_8,
- MIMENAME_GBK,
- MIMENAME_GB18030,
- MIMENAME_GB2312,
- MIMENAME_BIG5,
- MIMENAME_UCS2,
- MIMENAME_UTF_16,
- MIMENAME_HZ_GB_2312,
- };
-
- private static final SparseArray<String> MIBENUM_TO_NAME_MAP;
-
- private static final SimpleArrayMap<String, Integer> NAME_TO_MIBENUM_MAP;
-
- static {
- // Create the HashMaps.
- MIBENUM_TO_NAME_MAP = new SparseArray<String>();
- NAME_TO_MIBENUM_MAP = new SimpleArrayMap<String, Integer>();
- assert (MIBENUM_NUMBERS.length == MIME_NAMES.length);
- final int count = MIBENUM_NUMBERS.length - 1;
- for (int i = 0; i <= count; i++) {
- MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
- NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
- }
- }
-
- private CharacterSets() {
- } // Non-instantiatable
-
- /**
- * Map an MIBEnum number to the name of the charset which this number
- * is assigned to by IANA.
- *
- * @param mibEnumValue An IANA assigned MIBEnum number.
- * @return The name string of the charset.
- */
- public static String getMimeName(final int mibEnumValue)
- throws UnsupportedEncodingException {
- final String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
- if (name == null) {
- throw new UnsupportedEncodingException();
- }
- return name;
- }
-
- /**
- * Map a well-known charset name to its assigned MIBEnum number.
- *
- * @param mimeName The charset name.
- * @return The MIBEnum number assigned by IANA for this charset.
- */
- public static int getMibEnumValue(final String mimeName)
- throws UnsupportedEncodingException {
- if (null == mimeName) {
- return -1;
- }
-
- final Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
- if (mibEnumValue == null) {
- throw new UnsupportedEncodingException();
- }
- return mibEnumValue;
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/DeliveryInd.java b/src/com/android/messaging/mmslib/pdu/DeliveryInd.java
deleted file mode 100644
index 5e1d873..0000000
--- a/src/com/android/messaging/mmslib/pdu/DeliveryInd.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-/**
- * M-Delivery.Ind Pdu.
- */
-public class DeliveryInd extends GenericPdu {
- /**
- * Empty constructor.
- * Since the Pdu corresponding to this class is constructed
- * by the Proxy-Relay server, this class is only instantiated
- * by the Pdu Parser.
- *
- * @throws InvalidHeaderValueException if error occurs.
- */
- public DeliveryInd() throws InvalidHeaderValueException {
- super();
- setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- DeliveryInd(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Get Date value.
- *
- * @return the value
- */
- public long getDate() {
- return mPduHeaders.getLongInteger(PduHeaders.DATE);
- }
-
- /**
- * Set Date value.
- *
- * @param value the value
- */
- public void setDate(long value) {
- mPduHeaders.setLongInteger(value, PduHeaders.DATE);
- }
-
- /**
- * Get Message-ID value.
- *
- * @return the value
- */
- public byte[] getMessageId() {
- return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Set Message-ID value.
- *
- * @param value the value, should not be null
- * @throws NullPointerException if the value is null.
- */
- public void setMessageId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Get Status value.
- *
- * @return the value
- */
- public int getStatus() {
- return mPduHeaders.getOctet(PduHeaders.STATUS);
- }
-
- /**
- * Set Status value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setStatus(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.STATUS);
- }
-
- /**
- * Get To value.
- *
- * @return the value
- */
- public EncodedStringValue[] getTo() {
- return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
- }
-
- /**
- * set To value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setTo(EncodedStringValue[] value) {
- mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
- }
-
- /*
- * Optional, not supported header fields:
- *
- * public byte[] getApplicId() {return null;}
- * public void setApplicId(byte[] value) {}
- *
- * public byte[] getAuxApplicId() {return null;}
- * public void getAuxApplicId(byte[] value) {}
- *
- * public byte[] getReplyApplicId() {return 0x00;}
- * public void setReplyApplicId(byte[] value) {}
- *
- * public EncodedStringValue getStatusText() {return null;}
- * public void setStatusText(EncodedStringValue value) {}
- */
-}
diff --git a/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java b/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java
deleted file mode 100644
index 1d3c9ea..0000000
--- a/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.messaging.mmslib.pdu;
-
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-
-/**
- * Encoded-string-value = Text-string | Value-length Char-set Text-string
- */
-public class EncodedStringValue implements Cloneable {
- private static final String TAG = "EncodedStringValue";
- private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = false;
-
- /**
- * The Char-set value.
- */
- private int mCharacterSet;
-
- /**
- * The Text-string value.
- */
- private byte[] mData;
-
- /**
- * Constructor.
- *
- * @param charset the Char-set value
- * @param data the Text-string value
- * @throws NullPointerException if Text-string value is null.
- */
- public EncodedStringValue(int charset, byte[] data) {
- // TODO: CharSet needs to be validated against MIBEnum.
- if (null == data) {
- throw new NullPointerException("EncodedStringValue: Text-string is null.");
- }
-
- mCharacterSet = charset;
- mData = new byte[data.length];
- System.arraycopy(data, 0, mData, 0, data.length);
- }
-
- /**
- * Constructor.
- *
- * @param data the Text-string value
- * @throws NullPointerException if Text-string value is null.
- */
- public EncodedStringValue(byte[] data) {
- this(CharacterSets.DEFAULT_CHARSET, data);
- }
-
- public EncodedStringValue(String data) {
- this(CharacterSets.DEFAULT_CHARSET, data);
- }
-
- /**
- * Constructor
- *
- * @param charset
- * @param data The text in Java String
- * @throws NullPointerException if Text-string value is null.
- */
- public EncodedStringValue(int charset, String data) {
- if (null == data) {
- throw new NullPointerException("EncodedStringValue: Text-string is null");
- }
- mCharacterSet = charset;
- try {
- mData = data.getBytes(CharacterSets.getMimeName(charset));
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Input encoding " + charset + " must be supported.", e);
- mData = data.getBytes();
- }
- }
-
- /**
- * Get Char-set value.
- *
- * @return the value
- */
- public int getCharacterSet() {
- return mCharacterSet;
- }
-
- /**
- * Set Char-set value.
- *
- * @param charset the Char-set value
- */
- public void setCharacterSet(int charset) {
- // TODO: CharSet needs to be validated against MIBEnum.
- mCharacterSet = charset;
- }
-
- /**
- * Get Text-string value.
- *
- * @return the value
- */
- public byte[] getTextString() {
- byte[] byteArray = new byte[mData.length];
-
- System.arraycopy(mData, 0, byteArray, 0, mData.length);
- return byteArray;
- }
-
- /**
- * Set Text-string value.
- *
- * @param textString the Text-string value
- * @throws NullPointerException if Text-string value is null.
- */
- public void setTextString(byte[] textString) {
- if (null == textString) {
- throw new NullPointerException("EncodedStringValue: Text-string is null.");
- }
-
- mData = new byte[textString.length];
- System.arraycopy(textString, 0, mData, 0, textString.length);
- }
-
- /**
- * Convert this object to a {@link java.lang.String}. If the encoding of
- * the EncodedStringValue is null or unsupported, it will be
- * treated as iso-8859-1 encoding.
- *
- * @return The decoded String.
- */
- public String getString() {
- if (CharacterSets.ANY_CHARSET == mCharacterSet) {
- return new String(mData); // system default encoding.
- } else {
- try {
- String name = CharacterSets.getMimeName(mCharacterSet);
- return new String(mData, name);
- } catch (UnsupportedEncodingException e) {
- if (LOCAL_LOGV) {
- Log.v(TAG, e.getMessage(), e);
- }
- try {
- return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
- } catch (UnsupportedEncodingException exception) {
- return new String(mData); // system default encoding.
- }
- }
- }
- }
-
- /**
- * Append to Text-string.
- *
- * @param textString the textString to append
- * @throws NullPointerException if the text String is null
- * or an IOException occured.
- */
- public void appendTextString(byte[] textString) {
- if (null == textString) {
- throw new NullPointerException("Text-string is null.");
- }
-
- if (null == mData) {
- mData = new byte[textString.length];
- System.arraycopy(textString, 0, mData, 0, textString.length);
- } else {
- ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
- try {
- newTextString.write(mData);
- newTextString.write(textString);
- } catch (IOException e) {
- e.printStackTrace();
- throw new NullPointerException(
- "appendTextString: failed when write a new Text-string");
- }
-
- mData = newTextString.toByteArray();
- }
- }
-
- /*
- * (non-Javadoc)
- * @see java.lang.Object#clone()
- */
- @Override
- public Object clone() throws CloneNotSupportedException {
- super.clone();
- int len = mData.length;
- byte[] dstBytes = new byte[len];
- System.arraycopy(mData, 0, dstBytes, 0, len);
-
- try {
- return new EncodedStringValue(mCharacterSet, dstBytes);
- } catch (Exception e) {
- Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
- e.printStackTrace();
- throw new CloneNotSupportedException(e.getMessage());
- }
- }
-
- /**
- * Split this encoded string around matches of the given pattern.
- *
- * @param pattern the delimiting pattern
- * @return the array of encoded strings computed by splitting this encoded
- * string around matches of the given pattern
- */
- public EncodedStringValue[] split(String pattern) {
- String[] temp = getString().split(pattern);
- EncodedStringValue[] ret = new EncodedStringValue[temp.length];
- for (int i = 0; i < ret.length; ++i) {
- try {
- ret[i] = new EncodedStringValue(mCharacterSet,
- temp[i].getBytes());
- } catch (NullPointerException exception) {
- // Can't arrive here
- return null;
- }
- }
- return ret;
- }
-
- /**
- * Extract an EncodedStringValue[] from a given String.
- */
- public static EncodedStringValue[] extract(String src) {
- String[] values = src.split(";");
-
- ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
- for (int i = 0; i < values.length; i++) {
- if (values[i].length() > 0) {
- list.add(new EncodedStringValue(values[i]));
- }
- }
-
- int len = list.size();
- if (len > 0) {
- return list.toArray(new EncodedStringValue[len]);
- } else {
- return null;
- }
- }
-
- /**
- * Concatenate an EncodedStringValue[] into a single String.
- */
- public static String concat(EncodedStringValue[] addr) {
- StringBuilder sb = new StringBuilder();
- int maxIndex = addr.length - 1;
- for (int i = 0; i <= maxIndex; i++) {
- sb.append(addr[i].getString());
- if (i < maxIndex) {
- sb.append(";");
- }
- }
-
- return sb.toString();
- }
-
- public static EncodedStringValue copy(EncodedStringValue value) {
- if (value == null) {
- return null;
- }
-
- return new EncodedStringValue(value.mCharacterSet, value.mData);
- }
-
- public static EncodedStringValue[] encodeStrings(String[] array) {
- int count = array.length;
- if (count > 0) {
- EncodedStringValue[] encodedArray = new EncodedStringValue[count];
- for (int i = 0; i < count; i++) {
- encodedArray[i] = new EncodedStringValue(array[i]);
- }
- return encodedArray;
- }
- return null;
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/GenericPdu.java b/src/com/android/messaging/mmslib/pdu/GenericPdu.java
deleted file mode 100644
index 178dc5a..0000000
--- a/src/com/android/messaging/mmslib/pdu/GenericPdu.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-public class GenericPdu {
- /**
- * The headers of pdu.
- */
- PduHeaders mPduHeaders = null;
-
- /**
- * Constructor.
- */
- public GenericPdu() {
- mPduHeaders = new PduHeaders();
- }
-
- /**
- * Constructor.
- *
- * @param headers Headers for this PDU.
- */
- GenericPdu(PduHeaders headers) {
- mPduHeaders = headers;
- }
-
- /**
- * Get the headers of this PDU.
- *
- * @return A PduHeaders of this PDU.
- */
- PduHeaders getPduHeaders() {
- return mPduHeaders;
- }
-
- /**
- * Get X-Mms-Message-Type field value.
- *
- * @return the X-Mms-Report-Allowed value
- */
- public int getMessageType() {
- return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
- }
-
- /**
- * Set X-Mms-Message-Type field value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- * @throws RuntimeException if field's value is not Octet.
- */
- public void setMessageType(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
- }
-
- /**
- * Get X-Mms-MMS-Version field value.
- *
- * @return the X-Mms-MMS-Version value
- */
- public int getMmsVersion() {
- return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
- }
-
- /**
- * Set X-Mms-MMS-Version field value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- * @throws RuntimeException if field's value is not Octet.
- */
- public void setMmsVersion(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
- }
-
- /**
- * Get From value.
- * From-value = Value-length
- * (Address-present-token Encoded-string-value | Insert-address-token)
- *
- * @return the value
- */
- public EncodedStringValue getFrom() {
- return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
- }
-
- /**
- * Set From value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setFrom(EncodedStringValue value) {
- mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java b/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java
deleted file mode 100644
index cb8c8c7..0000000
--- a/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-/**
- * Multimedia message PDU.
- */
-public class MultimediaMessagePdu extends GenericPdu {
- /**
- * The body.
- */
- private PduBody mMessageBody;
-
- /**
- * Constructor.
- */
- public MultimediaMessagePdu() {
- super();
- }
-
- /**
- * Constructor.
- *
- * @param header the header of this PDU
- * @param body the body of this PDU
- */
- public MultimediaMessagePdu(PduHeaders header, PduBody body) {
- super(header);
- mMessageBody = body;
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- MultimediaMessagePdu(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Get body of the PDU.
- *
- * @return the body
- */
- public PduBody getBody() {
- return mMessageBody;
- }
-
- /**
- * Set body of the PDU.
- *
- * @param body the body
- */
- public void setBody(PduBody body) {
- mMessageBody = body;
- }
-
- /**
- * Get subject.
- *
- * @return the value
- */
- public EncodedStringValue getSubject() {
- return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
- }
-
- /**
- * Set subject.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setSubject(EncodedStringValue value) {
- mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
- }
-
- /**
- * Get To value.
- *
- * @return the value
- */
- public EncodedStringValue[] getTo() {
- return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
- }
-
- /**
- * Add a "To" value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void addTo(EncodedStringValue value) {
- mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
- }
-
- /**
- * Get X-Mms-Priority value.
- *
- * @return the value
- */
- public int getPriority() {
- return mPduHeaders.getOctet(PduHeaders.PRIORITY);
- }
-
- /**
- * Set X-Mms-Priority value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setPriority(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
- }
-
- /**
- * Get Date value.
- *
- * @return the value
- */
- public long getDate() {
- return mPduHeaders.getLongInteger(PduHeaders.DATE);
- }
-
- /**
- * Set Date value in seconds.
- *
- * @param value the value
- */
- public void setDate(long value) {
- mPduHeaders.setLongInteger(value, PduHeaders.DATE);
- }
-
- /**
- * Get the message size
- *
- * @return the size of the message
- */
- public long getMessageSize() {
- return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/NotificationInd.java b/src/com/android/messaging/mmslib/pdu/NotificationInd.java
deleted file mode 100644
index 4cbbd30..0000000
--- a/src/com/android/messaging/mmslib/pdu/NotificationInd.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-/**
- * M-Notification.ind PDU.
- */
-public class NotificationInd extends GenericPdu {
- /**
- * Empty constructor.
- * Since the Pdu corresponding to this class is constructed
- * by the Proxy-Relay server, this class is only instantiated
- * by the Pdu Parser.
- *
- * @throws InvalidHeaderValueException if error occurs.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public NotificationInd() throws InvalidHeaderValueException {
- super();
- setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- NotificationInd(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Get X-Mms-Content-Class Value.
- *
- * @return the value
- */
- public int getContentClass() {
- return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
- }
-
- /**
- * Set X-Mms-Content-Class Value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setContentClass(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
- }
-
- /**
- * Get X-Mms-Content-Location value.
- * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
- * Content-location-value = Uri-value
- *
- * @return the value
- */
- public byte[] getContentLocation() {
- return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
- }
-
- /**
- * Set X-Mms-Content-Location value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setContentLocation(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
- }
-
- /**
- * Get X-Mms-Expiry value.
- *
- * Expiry-value = Value-length
- * (Absolute-token Date-value | Relative-token Delta-seconds-value)
- *
- * @return the value
- */
- public long getExpiry() {
- return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
- }
-
- /**
- * Set X-Mms-Expiry value.
- *
- * @param value the value
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setExpiry(long value) {
- mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
- }
-
- /**
- * Get From value.
- * From-value = Value-length
- * (Address-present-token Encoded-string-value | Insert-address-token)
- *
- * @return the value
- */
- public EncodedStringValue getFrom() {
- return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
- }
-
- /**
- * Set From value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setFrom(EncodedStringValue value) {
- mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
- }
-
- /**
- * Get X-Mms-Message-Class value.
- * Message-class-value = Class-identifier | Token-text
- * Class-identifier = Personal | Advertisement | Informational | Auto
- *
- * @return the value
- */
- public byte[] getMessageClass() {
- return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
- }
-
- /**
- * Set X-Mms-Message-Class value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setMessageClass(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
- }
-
- /**
- * Get X-Mms-Message-Size value.
- * Message-size-value = Long-integer
- *
- * @return the value
- */
- public long getMessageSize() {
- return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
- }
-
- /**
- * Set X-Mms-Message-Size value.
- *
- * @param value the value
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setMessageSize(long value) {
- mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
- }
-
- /**
- * Get subject.
- *
- * @return the value
- */
- public EncodedStringValue getSubject() {
- return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
- }
-
- /**
- * Set subject.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setSubject(EncodedStringValue value) {
- mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
- }
-
- /**
- * Get X-Mms-Transaction-Id.
- *
- * @return the value
- */
- public byte[] getTransactionId() {
- return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
- }
-
- /**
- * Set X-Mms-Transaction-Id.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setTransactionId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
- }
-
- /**
- * Get X-Mms-Delivery-Report Value.
- *
- * @return the value
- */
- public int getDeliveryReport() {
- return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
- }
-
- /**
- * Set X-Mms-Delivery-Report Value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setDeliveryReport(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
- }
-
- /*
- * Optional, not supported header fields:
- *
- * public byte[] getApplicId() {return null;}
- * public void setApplicId(byte[] value) {}
- *
- * public byte[] getAuxApplicId() {return null;}
- * public void getAuxApplicId(byte[] value) {}
- *
- * public byte getDrmContent() {return 0x00;}
- * public void setDrmContent(byte value) {}
- *
- * public byte getDistributionIndicator() {return 0x00;}
- * public void setDistributionIndicator(byte value) {}
- *
- * public ElementDescriptorValue getElementDescriptor() {return null;}
- * public void getElementDescriptor(ElementDescriptorValue value) {}
- *
- * public byte getPriority() {return 0x00;}
- * public void setPriority(byte value) {}
- *
- * public byte getRecommendedRetrievalMode() {return 0x00;}
- * public void setRecommendedRetrievalMode(byte value) {}
- *
- * public byte getRecommendedRetrievalModeText() {return 0x00;}
- * public void setRecommendedRetrievalModeText(byte value) {}
- *
- * public byte[] getReplaceId() {return 0x00;}
- * public void setReplaceId(byte[] value) {}
- *
- * public byte[] getReplyApplicId() {return 0x00;}
- * public void setReplyApplicId(byte[] value) {}
- *
- * public byte getReplyCharging() {return 0x00;}
- * public void setReplyCharging(byte value) {}
- *
- * public byte getReplyChargingDeadline() {return 0x00;}
- * public void setReplyChargingDeadline(byte value) {}
- *
- * public byte[] getReplyChargingId() {return 0x00;}
- * public void setReplyChargingId(byte[] value) {}
- *
- * public long getReplyChargingSize() {return 0;}
- * public void setReplyChargingSize(long value) {}
- *
- * public byte getStored() {return 0x00;}
- * public void setStored(byte value) {}
- */
-}
diff --git a/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java b/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java
deleted file mode 100644
index 7dabd89..0000000
--- a/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-/**
- * M-NofifyResp.ind PDU.
- */
-public class NotifyRespInd extends GenericPdu {
- /**
- * Constructor, used when composing a M-NotifyResp.ind pdu.
- *
- * @param mmsVersion current version of mms
- * @param transactionId the transaction-id value
- * @param status the status value
- * @throws InvalidHeaderValueException if parameters are invalid.
- * @throws NullPointerException if transactionId is null.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public NotifyRespInd(int mmsVersion,
- byte[] transactionId,
- int status) throws InvalidHeaderValueException {
- super();
- setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
- setMmsVersion(mmsVersion);
- setTransactionId(transactionId);
- setStatus(status);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- NotifyRespInd(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Get X-Mms-Report-Allowed field value.
- *
- * @return the X-Mms-Report-Allowed value
- */
- public int getReportAllowed() {
- return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
- }
-
- /**
- * Set X-Mms-Report-Allowed field value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setReportAllowed(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
- }
-
- /**
- * Set X-Mms-Status field value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setStatus(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.STATUS);
- }
-
- /**
- * GetX-Mms-Status field value.
- *
- * @return the X-Mms-Status value
- */
- public int getStatus() {
- return mPduHeaders.getOctet(PduHeaders.STATUS);
- }
-
- /**
- * Get X-Mms-Transaction-Id field value.
- *
- * @return the X-Mms-Report-Allowed value
- */
- public byte[] getTransactionId() {
- return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
- }
-
- /**
- * Set X-Mms-Transaction-Id field value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- * @throws RuntimeException if an undeclared error occurs.
- */
- public void setTransactionId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/PduBody.java b/src/com/android/messaging/mmslib/pdu/PduBody.java
deleted file mode 100644
index 8cbb2e6..0000000
--- a/src/com/android/messaging/mmslib/pdu/PduBody.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import java.util.Vector;
-
-public class PduBody {
- private Vector<PduPart> mParts = null;
-
- /**
- * Constructor.
- */
- public PduBody() {
- mParts = new Vector<PduPart>();
- }
-
- /**
- * Appends the specified part to the end of this body.
- *
- * @param part part to be appended
- * @return true when success, false when fail
- * @throws NullPointerException when part is null
- */
- public boolean addPart(PduPart part) {
- if (null == part) {
- throw new NullPointerException();
- }
-
- return mParts.add(part);
- }
-
- /**
- * Inserts the specified part at the specified position.
- *
- * @param index index at which the specified part is to be inserted
- * @param part part to be inserted
- * @throws NullPointerException when part is null
- */
- public void addPart(int index, PduPart part) {
- if (null == part) {
- throw new NullPointerException();
- }
-
- mParts.add(index, part);
- }
-
- /**
- * Remove all of the parts.
- */
- public void removeAll() {
- mParts.clear();
- }
-
- /**
- * Get the part at the specified position.
- *
- * @param index index of the part to return
- * @return part at the specified index
- */
- public PduPart getPart(int index) {
- return mParts.get(index);
- }
-
- /**
- * Get the number of parts.
- *
- * @return the number of parts
- */
- public int getPartsNum() {
- return mParts.size();
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/PduComposer.java b/src/com/android/messaging/mmslib/pdu/PduComposer.java
deleted file mode 100644
index d05a198..0000000
--- a/src/com/android/messaging/mmslib/pdu/PduComposer.java
+++ /dev/null
@@ -1,1260 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.messaging.mmslib.pdu;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.support.v4.util.SimpleArrayMap;
-import android.text.TextUtils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-
-public class PduComposer {
- /**
- * Address type.
- */
- private static final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1;
- private static final int PDU_EMAIL_ADDRESS_TYPE = 2;
- private static final int PDU_IPV4_ADDRESS_TYPE = 3;
- private static final int PDU_IPV6_ADDRESS_TYPE = 4;
- private static final int PDU_UNKNOWN_ADDRESS_TYPE = 5;
-
- /**
- * Address regular expression string.
- */
- static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+";
-
- static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" +
- "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}";
-
- static final String REGEXP_IPV6_ADDRESS_TYPE =
- "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
- "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
- "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}";
-
- static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" +
- "[0-9]{1,3}\\.{1}[0-9]{1,3}";
-
- /**
- * The postfix strings of address.
- */
- static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN";
- static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4";
- static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6";
-
- /**
- * Error values.
- */
- private static final int PDU_COMPOSE_SUCCESS = 0;
- private static final int PDU_COMPOSE_CONTENT_ERROR = 1;
- private static final int PDU_COMPOSE_FIELD_NOT_SET = 2;
- private static final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3;
-
- /**
- * WAP values defined in WSP spec.
- */
- private static final int QUOTED_STRING_FLAG = 34;
- private static final int END_STRING_FLAG = 0;
- private static final int LENGTH_QUOTE = 31;
- private static final int TEXT_MAX = 127;
- private static final int SHORT_INTEGER_MAX = 127;
- private static final int LONG_INTEGER_LENGTH_MAX = 8;
-
- /**
- * Block size when read data from InputStream.
- */
- private static final int PDU_COMPOSER_BLOCK_SIZE = 1024;
-
- /**
- * The output message.
- */
- protected ByteArrayOutputStream mMessage = null;
-
- /**
- * The PDU.
- */
- private GenericPdu mPdu = null;
-
- /**
- * Current visiting position of the mMessage.
- */
- protected int mPosition = 0;
-
- /**
- * Message compose buffer stack.
- */
- private BufferStack mStack = null;
-
- /**
- * Content resolver.
- */
- private final ContentResolver mResolver;
-
- /**
- * Header of this pdu.
- */
- private PduHeaders mPduHeader = null;
-
- /**
- * Map of all content type
- */
- private static SimpleArrayMap<String, Integer> mContentTypeMap = null;
-
- static {
- mContentTypeMap = new SimpleArrayMap<String, Integer>();
-
- int i;
- for (i = 0; i < PduContentTypes.contentTypes.length; i++) {
- mContentTypeMap.put(PduContentTypes.contentTypes[i], i);
- }
- }
-
- /**
- * Constructor.
- *
- * @param context the context
- * @param pdu the pdu to be composed
- */
- public PduComposer(final Context context, final GenericPdu pdu) {
- mPdu = pdu;
- mResolver = context.getContentResolver();
- mPduHeader = pdu.getPduHeaders();
- mStack = new BufferStack();
- mMessage = new ByteArrayOutputStream();
- mPosition = 0;
- }
-
- /**
- * Make the message. No need to check whether mandatory fields are set,
- * because the constructors of outgoing pdus are taking care of this.
- *
- * @return OutputStream of maked message. Return null if
- * the PDU is invalid.
- */
- public byte[] make() {
- // Get Message-type.
- final int type = mPdu.getMessageType();
-
- /* make the message */
- switch (type) {
- case PduHeaders.MESSAGE_TYPE_SEND_REQ:
- if (makeSendReqPdu() != PDU_COMPOSE_SUCCESS) {
- return null;
- }
- break;
- case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
- if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) {
- return null;
- }
- break;
- case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
- if (makeAckInd() != PDU_COMPOSE_SUCCESS) {
- return null;
- }
- break;
- case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
- if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) {
- return null;
- }
- break;
- case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
- if (makeNotificationInd() != PDU_COMPOSE_SUCCESS) {
- return null;
- }
- break;
- default:
- return null;
- }
-
- return mMessage.toByteArray();
- }
-
- /**
- * Copy buf to mMessage.
- */
- protected void arraycopy(final byte[] buf, final int pos, final int length) {
- mMessage.write(buf, pos, length);
- mPosition = mPosition + length;
- }
-
- /**
- * Append a byte to mMessage.
- */
- protected void append(final int value) {
- mMessage.write(value);
- mPosition++;
- }
-
- /**
- * Append short integer value to mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendShortInteger(final int value) {
- /*
- * From WAP-230-WSP-20010705-a:
- * Short-integer = OCTET
- * ; Integers in range 0-127 shall be encoded as a one octet value
- * ; with the most significant bit set to one (1xxx xxxx) and with
- * ; the value in the remaining least significant bits.
- * In our implementation, only low 7 bits are stored and otherwise
- * bits are ignored.
- */
- append((value | 0x80) & 0xff);
- }
-
- /**
- * Append an octet number between 128 and 255 into mMessage.
- * NOTE:
- * A value between 0 and 127 should be appended by using appendShortInteger.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendOctet(final int number) {
- append(number);
- }
-
- /**
- * Append a short length into mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendShortLength(final int value) {
- /*
- * From WAP-230-WSP-20010705-a:
- * Short-length = <Any octet 0-30>
- */
- append(value);
- }
-
- /**
- * Append long integer into mMessage. it's used for really long integers.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendLongInteger(final long longInt) {
- /*
- * From WAP-230-WSP-20010705-a:
- * Long-integer = Short-length Multi-octet-integer
- * ; The Short-length indicates the length of the Multi-octet-integer
- * Multi-octet-integer = 1*30 OCTET
- * ; The content octets shall be an unsigned integer value with the
- * ; most significant octet encoded first (big-endian representation).
- * ; The minimum number of octets must be used to encode the value.
- */
- int size;
- long temp = longInt;
-
- // Count the length of the long integer.
- for (size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) {
- temp = (temp >>> 8);
- }
-
- // Set Length.
- appendShortLength(size);
-
- // Count and set the long integer.
- int i;
- int shift = (size - 1) * 8;
-
- for (i = 0; i < size; i++) {
- append((int) ((longInt >>> shift) & 0xff));
- shift = shift - 8;
- }
- }
-
- /**
- * Append text string into mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendTextString(final byte[] text) {
- /*
- * From WAP-230-WSP-20010705-a:
- * Text-string = [Quote] *TEXT End-of-string
- * ; If the first character in the TEXT is in the range of 128-255,
- * ; a Quote character must precede it. Otherwise the Quote character
- * ;must be omitted. The Quote is not part of the contents.
- */
- if (((text[0]) & 0xff) > TEXT_MAX) { // No need to check for <= 255
- append(TEXT_MAX);
- }
-
- arraycopy(text, 0, text.length);
- append(0);
- }
-
- /**
- * Append text string into mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendTextString(final String str) {
- /*
- * From WAP-230-WSP-20010705-a:
- * Text-string = [Quote] *TEXT End-of-string
- * ; If the first character in the TEXT is in the range of 128-255,
- * ; a Quote character must precede it. Otherwise the Quote character
- * ;must be omitted. The Quote is not part of the contents.
- */
- appendTextString(str.getBytes());
- }
-
- /**
- * Append encoded string value to mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendEncodedString(final EncodedStringValue enStr) {
- /*
- * From OMA-TS-MMS-ENC-V1_3-20050927-C:
- * Encoded-string-value = Text-string | Value-length Char-set Text-string
- */
- assert (enStr != null);
-
- final int charset = enStr.getCharacterSet();
- final byte[] textString = enStr.getTextString();
- if (null == textString) {
- return;
- }
-
- /*
- * In the implementation of EncodedStringValue, the charset field will
- * never be 0. It will always be composed as
- * Encoded-string-value = Value-length Char-set Text-string
- */
- mStack.newbuf();
- final PositionMarker start = mStack.mark();
-
- appendShortInteger(charset);
- appendTextString(textString);
-
- final int len = start.getLength();
- mStack.pop();
- appendValueLength(len);
- mStack.copy();
- }
-
- /**
- * Append uintvar integer into mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendUintvarInteger(final long value) {
- /*
- * From WAP-230-WSP-20010705-a:
- * To encode a large unsigned integer, split it into 7-bit fragments
- * and place them in the payloads of multiple octets. The most significant
- * bits are placed in the first octets with the least significant bits
- * ending up in the last octet. All octets MUST set the Continue bit to 1
- * except the last octet, which MUST set the Continue bit to 0.
- */
- int i;
- long max = SHORT_INTEGER_MAX;
-
- for (i = 0; i < 5; i++) {
- if (value < max) {
- break;
- }
-
- max = (max << 7) | 0x7fL;
- }
-
- while (i > 0) {
- long temp = value >>> (i * 7);
- temp = temp & 0x7f;
-
- append((int) ((temp | 0x80) & 0xff));
-
- i--;
- }
-
- append((int) (value & 0x7f));
- }
-
- /**
- * Append date value into mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendDateValue(final long date) {
- /*
- * From OMA-TS-MMS-ENC-V1_3-20050927-C:
- * Date-value = Long-integer
- */
- appendLongInteger(date);
- }
-
- /**
- * Append value length to mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendValueLength(final long value) {
- /*
- * From WAP-230-WSP-20010705-a:
- * Value-length = Short-length | (Length-quote Length)
- * ; Value length is used to indicate the length of the value to follow
- * Short-length = <Any octet 0-30>
- * Length-quote = <Octet 31>
- * Length = Uintvar-integer
- */
- if (value < LENGTH_QUOTE) {
- appendShortLength((int) value);
- return;
- }
-
- append(LENGTH_QUOTE);
- appendUintvarInteger(value);
- }
-
- /**
- * Append quoted string to mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendQuotedString(final byte[] text) {
- /*
- * From WAP-230-WSP-20010705-a:
- * Quoted-string = <Octet 34> *TEXT End-of-string
- * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
- * ;quotation-marks <"> removed.
- */
- append(QUOTED_STRING_FLAG);
- arraycopy(text, 0, text.length);
- append(END_STRING_FLAG);
- }
-
- /**
- * Append quoted string to mMessage.
- * This implementation doesn't check the validity of parameter, since it
- * assumes that the values are validated in the GenericPdu setter methods.
- */
- protected void appendQuotedString(final String str) {
- /*
- * From WAP-230-WSP-20010705-a:
- * Quoted-string = <Octet 34> *TEXT End-of-string
- * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
- * ;quotation-marks <"> removed.
- */
- appendQuotedString(str.getBytes());
- }
-
- private EncodedStringValue appendAddressType(final EncodedStringValue address) {
- EncodedStringValue temp = null;
-
- try {
- final int addressType = checkAddressType(address.getString());
- temp = EncodedStringValue.copy(address);
- if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) {
- // Phone number.
- temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes());
- } else if (PDU_IPV4_ADDRESS_TYPE == addressType) {
- // Ipv4 address.
- temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes());
- } else if (PDU_IPV6_ADDRESS_TYPE == addressType) {
- // Ipv6 address.
- temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes());
- }
- } catch (final NullPointerException e) {
- return null;
- }
-
- return temp;
- }
-
- /**
- * Append header to mMessage.
- */
- private int appendHeader(final int field) {
- switch (field) {
- case PduHeaders.MMS_VERSION:
- appendOctet(field);
-
- final int version = mPduHeader.getOctet(field);
- if (0 == version) {
- appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
- } else {
- appendShortInteger(version);
- }
-
- break;
-
- case PduHeaders.MESSAGE_ID:
- case PduHeaders.TRANSACTION_ID:
- case PduHeaders.CONTENT_LOCATION:
- final byte[] textString = mPduHeader.getTextString(field);
- if (null == textString) {
- return PDU_COMPOSE_FIELD_NOT_SET;
- }
-
- appendOctet(field);
- appendTextString(textString);
- break;
-
- case PduHeaders.TO:
- case PduHeaders.BCC:
- case PduHeaders.CC:
- final EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field);
-
- if (null == addr) {
- return PDU_COMPOSE_FIELD_NOT_SET;
- }
-
- EncodedStringValue temp;
- for (int i = 0; i < addr.length; i++) {
- temp = appendAddressType(addr[i]);
- if (temp == null) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- appendOctet(field);
- appendEncodedString(temp);
- }
- break;
-
- case PduHeaders.FROM:
- // Value-length (Address-present-token Encoded-string-value | Insert-address-token)
- appendOctet(field);
-
- final EncodedStringValue from = mPduHeader.getEncodedStringValue(field);
- if ((from == null)
- || TextUtils.isEmpty(from.getString())
- || new String(from.getTextString()).equals(
- PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
- // Length of from = 1
- append(1);
- // Insert-address-token = <Octet 129>
- append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN);
- } else {
- mStack.newbuf();
- final PositionMarker fstart = mStack.mark();
-
- // Address-present-token = <Octet 128>
- append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN);
-
- temp = appendAddressType(from);
- if (temp == null) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- appendEncodedString(temp);
-
- final int flen = fstart.getLength();
- mStack.pop();
- appendValueLength(flen);
- mStack.copy();
- }
- break;
-
- case PduHeaders.READ_STATUS:
- case PduHeaders.STATUS:
- case PduHeaders.REPORT_ALLOWED:
- case PduHeaders.PRIORITY:
- case PduHeaders.DELIVERY_REPORT:
- case PduHeaders.READ_REPORT:
- final int octet = mPduHeader.getOctet(field);
- if (0 == octet) {
- return PDU_COMPOSE_FIELD_NOT_SET;
- }
-
- appendOctet(field);
- appendOctet(octet);
- break;
-
- case PduHeaders.DATE:
- final long date = mPduHeader.getLongInteger(field);
- if (-1 == date) {
- return PDU_COMPOSE_FIELD_NOT_SET;
- }
-
- appendOctet(field);
- appendDateValue(date);
- break;
-
- case PduHeaders.SUBJECT:
- final EncodedStringValue enString =
- mPduHeader.getEncodedStringValue(field);
- if (null == enString) {
- return PDU_COMPOSE_FIELD_NOT_SET;
- }
-
- appendOctet(field);
- appendEncodedString(enString);
- break;
-
- case PduHeaders.MESSAGE_CLASS:
- final byte[] messageClass = mPduHeader.getTextString(field);
- if (null == messageClass) {
- return PDU_COMPOSE_FIELD_NOT_SET;
- }
-
- appendOctet(field);
- if (Arrays.equals(messageClass,
- PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) {
- appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT);
- } else if (Arrays.equals(messageClass,
- PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) {
- appendOctet(PduHeaders.MESSAGE_CLASS_AUTO);
- } else if (Arrays.equals(messageClass,
- PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) {
- appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL);
- } else if (Arrays.equals(messageClass,
- PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) {
- appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL);
- } else {
- appendTextString(messageClass);
- }
- break;
-
- case PduHeaders.EXPIRY:
- case PduHeaders.MESSAGE_SIZE:
- final long value = mPduHeader.getLongInteger(field);
- if (-1 == value) {
- return PDU_COMPOSE_FIELD_NOT_SET;
- }
-
- appendOctet(field);
-
- mStack.newbuf();
- final PositionMarker valueStart = mStack.mark();
-
- append(PduHeaders.VALUE_RELATIVE_TOKEN);
- appendLongInteger(value);
-
- final int valueLength = valueStart.getLength();
- mStack.pop();
- appendValueLength(valueLength);
- mStack.copy();
- break;
-
- default:
- return PDU_COMPOSE_FIELD_NOT_SUPPORTED;
- }
-
- return PDU_COMPOSE_SUCCESS;
- }
-
- /**
- * Make ReadRec.Ind.
- */
- private int makeReadRecInd() {
- if (mMessage == null) {
- mMessage = new ByteArrayOutputStream();
- mPosition = 0;
- }
-
- // X-Mms-Message-Type
- appendOctet(PduHeaders.MESSAGE_TYPE);
- appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
-
- // X-Mms-MMS-Version
- if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // Message-ID
- if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // To
- if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // From
- if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // Date Optional
- appendHeader(PduHeaders.DATE);
-
- // X-Mms-Read-Status
- if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // X-Mms-Applic-ID Optional(not support)
- // X-Mms-Reply-Applic-ID Optional(not support)
- // X-Mms-Aux-Applic-Info Optional(not support)
-
- return PDU_COMPOSE_SUCCESS;
- }
-
- /**
- * Make NotifyResp.Ind.
- */
- private int makeNotifyResp() {
- if (mMessage == null) {
- mMessage = new ByteArrayOutputStream();
- mPosition = 0;
- }
-
- // X-Mms-Message-Type
- appendOctet(PduHeaders.MESSAGE_TYPE);
- appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
-
- // X-Mms-Transaction-ID
- if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // X-Mms-MMS-Version
- if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // X-Mms-Status
- if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // X-Mms-Report-Allowed Optional (not support)
- return PDU_COMPOSE_SUCCESS;
- }
-
- /**
- * Make Acknowledge.Ind.
- */
- private int makeAckInd() {
- if (mMessage == null) {
- mMessage = new ByteArrayOutputStream();
- mPosition = 0;
- }
-
- // X-Mms-Message-Type
- appendOctet(PduHeaders.MESSAGE_TYPE);
- appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
-
- // X-Mms-Transaction-ID
- if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // X-Mms-MMS-Version
- if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // X-Mms-Report-Allowed Optional
- appendHeader(PduHeaders.REPORT_ALLOWED);
-
- return PDU_COMPOSE_SUCCESS;
- }
-
- /**
- * Make Acknowledge.Ind.
- */
- private int makeNotificationInd() {
- if (mMessage == null) {
- mMessage = new ByteArrayOutputStream();
- mPosition = 0;
- }
-
- // X-Mms-Message-Type
- appendOctet(PduHeaders.MESSAGE_TYPE);
- appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
-
- // X-Mms-Transaction-ID
- if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // X-Mms-MMS-Version
- if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // From
- if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // Subject Optional
- appendHeader(PduHeaders.SUBJECT);
-
- // Expiry
- if (appendHeader(PduHeaders.MESSAGE_CLASS) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // Expiry
- if (appendHeader(PduHeaders.MESSAGE_SIZE) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // Expiry
- if (appendHeader(PduHeaders.EXPIRY) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // X-Mms-Content-Location
- if (appendHeader(PduHeaders.CONTENT_LOCATION) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- return PDU_COMPOSE_SUCCESS;
- }
-
- /**
- * Make Send.req.
- */
- private int makeSendReqPdu() {
- if (mMessage == null) {
- mMessage = new ByteArrayOutputStream();
- mPosition = 0;
- }
-
- // X-Mms-Message-Type
- appendOctet(PduHeaders.MESSAGE_TYPE);
- appendOctet(PduHeaders.MESSAGE_TYPE_SEND_REQ);
-
- // X-Mms-Transaction-ID
- appendOctet(PduHeaders.TRANSACTION_ID);
-
- final byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID);
- if (trid == null) {
- // Transaction-ID should be set(by Transaction) before make().
- throw new IllegalArgumentException("Transaction-ID is null.");
- }
- appendTextString(trid);
-
- // X-Mms-MMS-Version
- if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // Date Date-value Optional.
- appendHeader(PduHeaders.DATE);
-
- // From
- if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- boolean recipient = false;
-
- // To
- if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) {
- recipient = true;
- }
-
- // Cc
- if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) {
- recipient = true;
- }
-
- // Bcc
- if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) {
- recipient = true;
- }
-
- // Need at least one of "cc", "bcc" and "to".
- if (false == recipient) {
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // Subject Optional
- appendHeader(PduHeaders.SUBJECT);
-
- // X-Mms-Message-Class Optional
- // Message-class-value = Class-identifier | Token-text
- appendHeader(PduHeaders.MESSAGE_CLASS);
-
- // X-Mms-Expiry Optional
- appendHeader(PduHeaders.EXPIRY);
-
- // X-Mms-Priority Optional
- appendHeader(PduHeaders.PRIORITY);
-
- // X-Mms-Delivery-Report Optional
- appendHeader(PduHeaders.DELIVERY_REPORT);
-
- // X-Mms-Read-Report Optional
- appendHeader(PduHeaders.READ_REPORT);
-
- // Content-Type
- appendOctet(PduHeaders.CONTENT_TYPE);
-
- // Message body
- return makeMessageBody();
- }
-
- /**
- * Make message body.
- */
- private int makeMessageBody() {
- // 1. add body informations
- mStack.newbuf(); // Switching buffer because we need to
-
- final PositionMarker ctStart = mStack.mark();
-
- // This contentTypeIdentifier should be used for type of attachment...
- final String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE));
- final Integer contentTypeIdentifier = mContentTypeMap.get(contentType);
- if (contentTypeIdentifier == null) {
- // content type is mandatory
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- appendShortInteger(contentTypeIdentifier.intValue());
-
- // content-type parameter: start
- final PduBody body = ((SendReq) mPdu).getBody();
- if (null == body || body.getPartsNum() == 0) {
- // empty message
- appendUintvarInteger(0);
- mStack.pop();
- mStack.copy();
- return PDU_COMPOSE_SUCCESS;
- }
-
- PduPart part;
- try {
- part = body.getPart(0);
-
- final byte[] start = part.getContentId();
- if (start != null) {
- appendOctet(PduPart.P_DEP_START);
- if (('<' == start[0]) && ('>' == start[start.length - 1])) {
- appendTextString(start);
- } else {
- appendTextString("<" + new String(start) + ">");
- }
- }
-
- // content-type parameter: type
- appendOctet(PduPart.P_CT_MR_TYPE);
- appendTextString(part.getContentType());
- } catch (final ArrayIndexOutOfBoundsException e) {
- e.printStackTrace();
- }
-
- final int ctLength = ctStart.getLength();
- mStack.pop();
- appendValueLength(ctLength);
- mStack.copy();
-
- // 3. add content
- final int partNum = body.getPartsNum();
- appendUintvarInteger(partNum);
- for (int i = 0; i < partNum; i++) {
- part = body.getPart(i);
- mStack.newbuf(); // Leaving space for header lengh and data length
- final PositionMarker attachment = mStack.mark();
-
- mStack.newbuf(); // Leaving space for Content-Type length
- final PositionMarker contentTypeBegin = mStack.mark();
-
- final byte[] partContentType = part.getContentType();
-
- if (partContentType == null) {
- // content type is mandatory
- return PDU_COMPOSE_CONTENT_ERROR;
- }
-
- // content-type value
- final Integer partContentTypeIdentifier =
- mContentTypeMap.get(new String(partContentType));
- if (partContentTypeIdentifier == null) {
- appendTextString(partContentType);
- } else {
- appendShortInteger(partContentTypeIdentifier.intValue());
- }
-
- /* Content-type parameter : name.
- * The value of name, filename, content-location is the same.
- * Just one of them is enough for this PDU.
- */
- byte[] name = part.getName();
-
- if (null == name) {
- name = part.getFilename();
-
- if (null == name) {
- name = part.getContentLocation();
-
- if (null == name) {
- /* at lease one of name, filename, Content-location
- * should be available.
- */
- // I found that an mms received from tmomail.net will include a SMIL part
- // that has no name. That would cause the code here to return
- // PDU_COMPOSE_CONTENT_ERROR when a user tried to forward the message. The
- // message would never send and the user would be stuck in a retry
- // situation. Simply jam in any old name here to fix the problem.
- name = "smil.xml".getBytes();
- }
- }
- }
- appendOctet(PduPart.P_DEP_NAME);
- appendTextString(name);
-
- // content-type parameter : charset
- final int charset = part.getCharset();
- if (charset != 0) {
- appendOctet(PduPart.P_CHARSET);
- appendShortInteger(charset);
- }
-
- final int contentTypeLength = contentTypeBegin.getLength();
- mStack.pop();
- appendValueLength(contentTypeLength);
- mStack.copy();
-
- // content id
- final byte[] contentId = part.getContentId();
-
- if (null != contentId) {
- appendOctet(PduPart.P_CONTENT_ID);
- if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) {
- appendQuotedString(contentId);
- } else {
- appendQuotedString("<" + new String(contentId) + ">");
- }
- }
-
- // content-location
- final byte[] contentLocation = part.getContentLocation();
- if (null != contentLocation) {
- appendOctet(PduPart.P_CONTENT_LOCATION);
- appendTextString(contentLocation);
- }
-
- // content
- final int headerLength = attachment.getLength();
-
- int dataLength = 0; // Just for safety...
- final byte[] partData = part.getData();
-
- if (partData != null) {
- arraycopy(partData, 0, partData.length);
- dataLength = partData.length;
- } else {
- InputStream cr = null;
- try {
- final byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE];
- cr = mResolver.openInputStream(part.getDataUri());
- int len = 0;
- while ((len = cr.read(buffer)) != -1) {
- mMessage.write(buffer, 0, len);
- mPosition += len;
- dataLength += len;
- }
- } catch (final FileNotFoundException e) {
- return PDU_COMPOSE_CONTENT_ERROR;
- } catch (final IOException e) {
- return PDU_COMPOSE_CONTENT_ERROR;
- } catch (final RuntimeException e) {
- return PDU_COMPOSE_CONTENT_ERROR;
- } finally {
- if (cr != null) {
- try {
- cr.close();
- } catch (final IOException e) {
- // Nothing to do
- }
- }
- }
- }
-
- if (dataLength != (attachment.getLength() - headerLength)) {
- throw new RuntimeException("BUG: Length sanity check failed");
- }
-
- mStack.pop();
- appendUintvarInteger(headerLength);
- appendUintvarInteger(dataLength);
- mStack.copy();
- }
-
- return PDU_COMPOSE_SUCCESS;
- }
-
- /**
- * Record current message informations.
- */
- private static class LengthRecordNode {
-
- ByteArrayOutputStream currentMessage = null;
-
- public int currentPosition = 0;
-
- public LengthRecordNode next = null;
- }
-
- /**
- * Mark current message position and stact size.
- */
- private class PositionMarker {
-
- private int c_pos; // Current position
-
- private int currentStackSize; // Current stack size
-
- int getLength() {
- // If these assert fails, likely that you are finding the
- // size of buffer that is deep in BufferStack you can only
- // find the length of the buffer that is on top
- if (currentStackSize != mStack.stackSize) {
- throw new RuntimeException("BUG: Invalid call to getLength()");
- }
-
- return mPosition - c_pos;
- }
- }
-
- /**
- * This implementation can be OPTIMIZED to use only
- * 2 buffers. This optimization involves changing BufferStack
- * only... Its usage (interface) will not change.
- */
- private class BufferStack {
-
- private LengthRecordNode stack = null;
-
- private LengthRecordNode toCopy = null;
-
- int stackSize = 0;
-
- /**
- * Create a new message buffer and push it into the stack.
- */
- void newbuf() {
- // You can't create a new buff when toCopy != null
- // That is after calling pop() and before calling copy()
- // If you do, it is a bug
- if (toCopy != null) {
- throw new RuntimeException("BUG: Invalid newbuf() before copy()");
- }
-
- final LengthRecordNode temp = new LengthRecordNode();
-
- temp.currentMessage = mMessage;
- temp.currentPosition = mPosition;
-
- temp.next = stack;
- stack = temp;
-
- stackSize = stackSize + 1;
-
- mMessage = new ByteArrayOutputStream();
- mPosition = 0;
- }
-
- /**
- * Pop the message before and record current message in the stack.
- */
- void pop() {
- final ByteArrayOutputStream currentMessage = mMessage;
- final int currentPosition = mPosition;
-
- mMessage = stack.currentMessage;
- mPosition = stack.currentPosition;
-
- toCopy = stack;
- // Re using the top element of the stack to avoid memory allocation
-
- stack = stack.next;
- stackSize = stackSize - 1;
-
- toCopy.currentMessage = currentMessage;
- toCopy.currentPosition = currentPosition;
- }
-
- /**
- * Append current message to the message before.
- */
- void copy() {
- arraycopy(toCopy.currentMessage.toByteArray(), 0,
- toCopy.currentPosition);
-
- toCopy = null;
- }
-
- /**
- * Mark current message position
- */
- PositionMarker mark() {
- final PositionMarker m = new PositionMarker();
-
- m.c_pos = mPosition;
- m.currentStackSize = stackSize;
-
- return m;
- }
- }
-
- /**
- * Check address type.
- *
- * @param address address string without the postfix stinng type,
- * such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4"
- * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number,
- * PDU_EMAIL_ADDRESS_TYPE if it is email address,
- * PDU_IPV4_ADDRESS_TYPE if it is ipv4 address,
- * PDU_IPV6_ADDRESS_TYPE if it is ipv6 address,
- * PDU_UNKNOWN_ADDRESS_TYPE if it is unknown.
- */
- protected static int checkAddressType(final String address) {
- /**
- * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8.
- * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode)
- * e-mail = mailbox; to the definition of mailbox as described in
- * section 3.4 of [RFC2822], but excluding the
- * obsolete definitions as indicated by the "obs-" prefix.
- * device-address = ( global-phone-number "/TYPE=PLMN" )
- * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" )
- * / ( escaped-value "/TYPE=" address-type )
- *
- * global-phone-number = ["+"] 1*( DIGIT / written-sep )
- * written-sep =("-"/".")
- *
- * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value
- *
- * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373
- */
-
- if (null == address) {
- return PDU_UNKNOWN_ADDRESS_TYPE;
- }
-
- if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) {
- // Ipv4 address.
- return PDU_IPV4_ADDRESS_TYPE;
- } else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) {
- // Phone number.
- return PDU_PHONE_NUMBER_ADDRESS_TYPE;
- } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) {
- // Email address.
- return PDU_EMAIL_ADDRESS_TYPE;
- } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) {
- // Ipv6 address.
- return PDU_IPV6_ADDRESS_TYPE;
- } else {
- // Unknown address.
- return PDU_UNKNOWN_ADDRESS_TYPE;
- }
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/PduContentTypes.java b/src/com/android/messaging/mmslib/pdu/PduContentTypes.java
deleted file mode 100644
index f2cebf1..0000000
--- a/src/com/android/messaging/mmslib/pdu/PduContentTypes.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-public class PduContentTypes {
- /**
- * All content types. From:
- * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
- */
- static final String[] contentTypes = {
- "*/*", /* 0x00 */
- "text/*", /* 0x01 */
- "text/html", /* 0x02 */
- "text/plain", /* 0x03 */
- "text/x-hdml", /* 0x04 */
- "text/x-ttml", /* 0x05 */
- "text/x-vCalendar", /* 0x06 */
- "text/x-vCard", /* 0x07 */
- "text/vnd.wap.wml", /* 0x08 */
- "text/vnd.wap.wmlscript", /* 0x09 */
- "text/vnd.wap.wta-event", /* 0x0A */
- "multipart/*", /* 0x0B */
- "multipart/mixed", /* 0x0C */
- "multipart/form-data", /* 0x0D */
- "multipart/byterantes", /* 0x0E */
- "multipart/alternative", /* 0x0F */
- "application/*", /* 0x10 */
- "application/java-vm", /* 0x11 */
- "application/x-www-form-urlencoded", /* 0x12 */
- "application/x-hdmlc", /* 0x13 */
- "application/vnd.wap.wmlc", /* 0x14 */
- "application/vnd.wap.wmlscriptc", /* 0x15 */
- "application/vnd.wap.wta-eventc", /* 0x16 */
- "application/vnd.wap.uaprof", /* 0x17 */
- "application/vnd.wap.wtls-ca-certificate", /* 0x18 */
- "application/vnd.wap.wtls-user-certificate", /* 0x19 */
- "application/x-x509-ca-cert", /* 0x1A */
- "application/x-x509-user-cert", /* 0x1B */
- "image/*", /* 0x1C */
- "image/gif", /* 0x1D */
- "image/jpeg", /* 0x1E */
- "image/tiff", /* 0x1F */
- "image/png", /* 0x20 */
- "image/vnd.wap.wbmp", /* 0x21 */
- "application/vnd.wap.multipart.*", /* 0x22 */
- "application/vnd.wap.multipart.mixed", /* 0x23 */
- "application/vnd.wap.multipart.form-data", /* 0x24 */
- "application/vnd.wap.multipart.byteranges", /* 0x25 */
- "application/vnd.wap.multipart.alternative", /* 0x26 */
- "application/xml", /* 0x27 */
- "text/xml", /* 0x28 */
- "application/vnd.wap.wbxml", /* 0x29 */
- "application/x-x968-cross-cert", /* 0x2A */
- "application/x-x968-ca-cert", /* 0x2B */
- "application/x-x968-user-cert", /* 0x2C */
- "text/vnd.wap.si", /* 0x2D */
- "application/vnd.wap.sic", /* 0x2E */
- "text/vnd.wap.sl", /* 0x2F */
- "application/vnd.wap.slc", /* 0x30 */
- "text/vnd.wap.co", /* 0x31 */
- "application/vnd.wap.coc", /* 0x32 */
- "application/vnd.wap.multipart.related", /* 0x33 */
- "application/vnd.wap.sia", /* 0x34 */
- "text/vnd.wap.connectivity-xml", /* 0x35 */
- "application/vnd.wap.connectivity-wbxml", /* 0x36 */
- "application/pkcs7-mime", /* 0x37 */
- "application/vnd.wap.hashed-certificate", /* 0x38 */
- "application/vnd.wap.signed-certificate", /* 0x39 */
- "application/vnd.wap.cert-response", /* 0x3A */
- "application/xhtml+xml", /* 0x3B */
- "application/wml+xml", /* 0x3C */
- "text/css", /* 0x3D */
- "application/vnd.wap.mms-message", /* 0x3E */
- "application/vnd.wap.rollover-certificate", /* 0x3F */
- "application/vnd.wap.locc+wbxml", /* 0x40 */
- "application/vnd.wap.loc+xml", /* 0x41 */
- "application/vnd.syncml.dm+wbxml", /* 0x42 */
- "application/vnd.syncml.dm+xml", /* 0x43 */
- "application/vnd.syncml.notification", /* 0x44 */
- "application/vnd.wap.xhtml+xml", /* 0x45 */
- "application/vnd.wv.csp.cir", /* 0x46 */
- "application/vnd.oma.dd+xml", /* 0x47 */
- "application/vnd.oma.drm.message", /* 0x48 */
- "application/vnd.oma.drm.content", /* 0x49 */
- "application/vnd.oma.drm.rights+xml", /* 0x4A */
- "application/vnd.oma.drm.rights+wbxml", /* 0x4B */
- "application/vnd.wv.csp+xml", /* 0x4C */
- "application/vnd.wv.csp+wbxml", /* 0x4D */
- "application/vnd.syncml.ds.notification", /* 0x4E */
- "audio/*", /* 0x4F */
- "video/*", /* 0x50 */
- "application/vnd.oma.dd2+xml", /* 0x51 */
- "application/mikey" /* 0x52 */
- };
-}
diff --git a/src/com/android/messaging/mmslib/pdu/PduHeaders.java b/src/com/android/messaging/mmslib/pdu/PduHeaders.java
deleted file mode 100644
index 96d18ee..0000000
--- a/src/com/android/messaging/mmslib/pdu/PduHeaders.java
+++ /dev/null
@@ -1,743 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import android.util.SparseArray;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-import java.util.ArrayList;
-
-public class PduHeaders {
- /**
- * All pdu header fields.
- */
- public static final int BCC = 0x81;
- public static final int CC = 0x82;
- public static final int CONTENT_LOCATION = 0x83;
- public static final int CONTENT_TYPE = 0x84;
- public static final int DATE = 0x85;
- public static final int DELIVERY_REPORT = 0x86;
- public static final int DELIVERY_TIME = 0x87;
- public static final int EXPIRY = 0x88;
- public static final int FROM = 0x89;
- public static final int MESSAGE_CLASS = 0x8A;
- public static final int MESSAGE_ID = 0x8B;
- public static final int MESSAGE_TYPE = 0x8C;
- public static final int MMS_VERSION = 0x8D;
- public static final int MESSAGE_SIZE = 0x8E;
- public static final int PRIORITY = 0x8F;
-
- public static final int READ_REPLY = 0x90;
- public static final int READ_REPORT = 0x90;
- public static final int REPORT_ALLOWED = 0x91;
- public static final int RESPONSE_STATUS = 0x92;
- public static final int RESPONSE_TEXT = 0x93;
- public static final int SENDER_VISIBILITY = 0x94;
- public static final int STATUS = 0x95;
- public static final int SUBJECT = 0x96;
- public static final int TO = 0x97;
- public static final int TRANSACTION_ID = 0x98;
- public static final int RETRIEVE_STATUS = 0x99;
- public static final int RETRIEVE_TEXT = 0x9A;
- public static final int READ_STATUS = 0x9B;
- public static final int REPLY_CHARGING = 0x9C;
- public static final int REPLY_CHARGING_DEADLINE = 0x9D;
- public static final int REPLY_CHARGING_ID = 0x9E;
- public static final int REPLY_CHARGING_SIZE = 0x9F;
-
- public static final int PREVIOUSLY_SENT_BY = 0xA0;
- public static final int PREVIOUSLY_SENT_DATE = 0xA1;
- public static final int STORE = 0xA2;
- public static final int MM_STATE = 0xA3;
- public static final int MM_FLAGS = 0xA4;
- public static final int STORE_STATUS = 0xA5;
- public static final int STORE_STATUS_TEXT = 0xA6;
- public static final int STORED = 0xA7;
- public static final int ATTRIBUTES = 0xA8;
- public static final int TOTALS = 0xA9;
- public static final int MBOX_TOTALS = 0xAA;
- public static final int QUOTAS = 0xAB;
- public static final int MBOX_QUOTAS = 0xAC;
- public static final int MESSAGE_COUNT = 0xAD;
- public static final int CONTENT = 0xAE;
- public static final int START = 0xAF;
-
- public static final int ADDITIONAL_HEADERS = 0xB0;
- public static final int DISTRIBUTION_INDICATOR = 0xB1;
- public static final int ELEMENT_DESCRIPTOR = 0xB2;
- public static final int LIMIT = 0xB3;
- public static final int RECOMMENDED_RETRIEVAL_MODE = 0xB4;
- public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
- public static final int STATUS_TEXT = 0xB6;
- public static final int APPLIC_ID = 0xB7;
- public static final int REPLY_APPLIC_ID = 0xB8;
- public static final int AUX_APPLIC_ID = 0xB9;
- public static final int CONTENT_CLASS = 0xBA;
- public static final int DRM_CONTENT = 0xBB;
- public static final int ADAPTATION_ALLOWED = 0xBC;
- public static final int REPLACE_ID = 0xBD;
- public static final int CANCEL_ID = 0xBE;
- public static final int CANCEL_STATUS = 0xBF;
-
- /**
- * X-Mms-Message-Type field types.
- */
- public static final int MESSAGE_TYPE_SEND_REQ = 0x80;
- public static final int MESSAGE_TYPE_SEND_CONF = 0x81;
- public static final int MESSAGE_TYPE_NOTIFICATION_IND = 0x82;
- public static final int MESSAGE_TYPE_NOTIFYRESP_IND = 0x83;
- public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
- public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND = 0x85;
- public static final int MESSAGE_TYPE_DELIVERY_IND = 0x86;
- public static final int MESSAGE_TYPE_READ_REC_IND = 0x87;
- public static final int MESSAGE_TYPE_READ_ORIG_IND = 0x88;
- public static final int MESSAGE_TYPE_FORWARD_REQ = 0x89;
- public static final int MESSAGE_TYPE_FORWARD_CONF = 0x8A;
- public static final int MESSAGE_TYPE_MBOX_STORE_REQ = 0x8B;
- public static final int MESSAGE_TYPE_MBOX_STORE_CONF = 0x8C;
- public static final int MESSAGE_TYPE_MBOX_VIEW_REQ = 0x8D;
- public static final int MESSAGE_TYPE_MBOX_VIEW_CONF = 0x8E;
- public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ = 0x8F;
- public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF = 0x90;
- public static final int MESSAGE_TYPE_MBOX_DELETE_REQ = 0x91;
- public static final int MESSAGE_TYPE_MBOX_DELETE_CONF = 0x92;
- public static final int MESSAGE_TYPE_MBOX_DESCR = 0x93;
- public static final int MESSAGE_TYPE_DELETE_REQ = 0x94;
- public static final int MESSAGE_TYPE_DELETE_CONF = 0x95;
- public static final int MESSAGE_TYPE_CANCEL_REQ = 0x96;
- public static final int MESSAGE_TYPE_CANCEL_CONF = 0x97;
-
- /**
- * X-Mms-Delivery-Report |
- * X-Mms-Read-Report |
- * X-Mms-Report-Allowed |
- * X-Mms-Sender-Visibility |
- * X-Mms-Store |
- * X-Mms-Stored |
- * X-Mms-Totals |
- * X-Mms-Quotas |
- * X-Mms-Distribution-Indicator |
- * X-Mms-DRM-Content |
- * X-Mms-Adaptation-Allowed |
- * field types.
- */
- public static final int VALUE_YES = 0x80;
- public static final int VALUE_NO = 0x81;
-
- /**
- * Delivery-Time |
- * Expiry and Reply-Charging-Deadline |
- * field type components.
- */
- public static final int VALUE_ABSOLUTE_TOKEN = 0x80;
- public static final int VALUE_RELATIVE_TOKEN = 0x81;
-
- /**
- * X-Mms-MMS-Version field types.
- */
- public static final int MMS_VERSION_1_3 = ((1 << 4) | 3);
- public static final int MMS_VERSION_1_2 = ((1 << 4) | 2);
- public static final int MMS_VERSION_1_1 = ((1 << 4) | 1);
- public static final int MMS_VERSION_1_0 = ((1 << 4) | 0);
-
- // Current version is 1.2.
- public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2;
-
- /**
- * From field type components.
- */
- public static final int FROM_ADDRESS_PRESENT_TOKEN = 0x80;
- public static final int FROM_INSERT_ADDRESS_TOKEN = 0x81;
-
- public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
-
- public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
-
- /**
- * X-Mms-Status Field.
- */
- public static final int STATUS_EXPIRED = 0x80;
- public static final int STATUS_RETRIEVED = 0x81;
- public static final int STATUS_REJECTED = 0x82;
- public static final int STATUS_DEFERRED = 0x83;
- public static final int STATUS_UNRECOGNIZED = 0x84;
- public static final int STATUS_INDETERMINATE = 0x85;
- public static final int STATUS_FORWARDED = 0x86;
- public static final int STATUS_UNREACHABLE = 0x87;
-
- /**
- * MM-Flags field type components.
- */
- public static final int MM_FLAGS_ADD_TOKEN = 0x80;
- public static final int MM_FLAGS_REMOVE_TOKEN = 0x81;
- public static final int MM_FLAGS_FILTER_TOKEN = 0x82;
-
- /**
- * X-Mms-Message-Class field types.
- */
- public static final int MESSAGE_CLASS_PERSONAL = 0x80;
- public static final int MESSAGE_CLASS_ADVERTISEMENT = 0x81;
- public static final int MESSAGE_CLASS_INFORMATIONAL = 0x82;
- public static final int MESSAGE_CLASS_AUTO = 0x83;
-
- public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
-
- public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
-
- public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
-
- public static final String MESSAGE_CLASS_AUTO_STR = "auto";
-
- /**
- * X-Mms-Priority field types.
- */
- public static final int PRIORITY_LOW = 0x80;
- public static final int PRIORITY_NORMAL = 0x81;
- public static final int PRIORITY_HIGH = 0x82;
-
- /**
- * X-Mms-Response-Status field types.
- */
- public static final int RESPONSE_STATUS_OK = 0x80;
- public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED = 0x81;
- public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
-
- public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT = 0x83;
- public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
-
- public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND = 0x85;
- public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM = 0x86;
- public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
- public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE = 0x88;
- public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
-
- public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
- public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC2;
- public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC3;
- public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS = 0xC4;
-
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE
- = 0xE0;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED
- = 0xE1;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT
- = 0xE2;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED
- = 0xE3;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND
- = 0xE4;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED
- = 0xE5;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET
- = 0xE6;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED
- = 0xE7;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED
- = 0xE8;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED
- = 0xE9;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED
- = 0xEA;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID
- = 0xEB;
- public static final int RESPONSE_STATUS_ERROR_PERMANENT_END
- = 0xFF;
-
- /**
- * X-Mms-Retrieve-Status field types.
- */
- public static final int RETRIEVE_STATUS_OK = 0x80;
- public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
- public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC1;
- public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC2;
- public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
- public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
- public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE2;
- public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
- public static final int RETRIEVE_STATUS_ERROR_END = 0xFF;
-
- /**
- * X-Mms-Sender-Visibility field types.
- */
- public static final int SENDER_VISIBILITY_HIDE = 0x80;
- public static final int SENDER_VISIBILITY_SHOW = 0x81;
-
- /**
- * X-Mms-Read-Status field types.
- */
- public static final int READ_STATUS_READ = 0x80;
- public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
-
- /**
- * X-Mms-Cancel-Status field types.
- */
- public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
- public static final int CANCEL_STATUS_REQUEST_CORRUPTED = 0x81;
-
- /**
- * X-Mms-Reply-Charging field types.
- */
- public static final int REPLY_CHARGING_REQUESTED = 0x80;
- public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
- public static final int REPLY_CHARGING_ACCEPTED = 0x82;
- public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY = 0x83;
-
- /**
- * X-Mms-MM-State field types.
- */
- public static final int MM_STATE_DRAFT = 0x80;
- public static final int MM_STATE_SENT = 0x81;
- public static final int MM_STATE_NEW = 0x82;
- public static final int MM_STATE_RETRIEVED = 0x83;
- public static final int MM_STATE_FORWARDED = 0x84;
-
- /**
- * X-Mms-Recommended-Retrieval-Mode field types.
- */
- public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
-
- /**
- * X-Mms-Content-Class field types.
- */
- public static final int CONTENT_CLASS_TEXT = 0x80;
- public static final int CONTENT_CLASS_IMAGE_BASIC = 0x81;
- public static final int CONTENT_CLASS_IMAGE_RICH = 0x82;
- public static final int CONTENT_CLASS_VIDEO_BASIC = 0x83;
- public static final int CONTENT_CLASS_VIDEO_RICH = 0x84;
- public static final int CONTENT_CLASS_MEGAPIXEL = 0x85;
- public static final int CONTENT_CLASS_CONTENT_BASIC = 0x86;
- public static final int CONTENT_CLASS_CONTENT_RICH = 0x87;
-
- /**
- * X-Mms-Store-Status field types.
- */
- public static final int STORE_STATUS_SUCCESS = 0x80;
- public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
- public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC1;
- public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
- public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
- public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
- public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE3;
- public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL = 0xE4;
- public static final int STORE_STATUS_ERROR_END = 0xFF;
-
- /**
- * The map contains the value of all headers.
- */
- private SparseArray<Object> mHeaderMap = null;
-
- /**
- * Constructor of PduHeaders.
- */
- public PduHeaders() {
- mHeaderMap = new SparseArray<Object>();
- }
-
- /**
- * Get octet value by header field.
- *
- * @param field the field
- * @return the octet value of the pdu header
- * with specified header field. Return 0 if
- * the value is not set.
- */
- protected int getOctet(int field) {
- Integer octet = (Integer) mHeaderMap.get(field);
- if (null == octet) {
- return 0;
- }
-
- return octet;
- }
-
- /**
- * Set octet value to pdu header by header field.
- *
- * @param value the value
- * @param field the field
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- protected void setOctet(int value, int field)
- throws InvalidHeaderValueException {
- /**
- * Check whether this field can be set for specific
- * header and check validity of the field.
- */
- switch (field) {
- case REPORT_ALLOWED:
- case ADAPTATION_ALLOWED:
- case DELIVERY_REPORT:
- case DRM_CONTENT:
- case DISTRIBUTION_INDICATOR:
- case QUOTAS:
- case READ_REPORT:
- case STORE:
- case STORED:
- case TOTALS:
- case SENDER_VISIBILITY:
- if ((VALUE_YES != value) && (VALUE_NO != value)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case READ_STATUS:
- if ((READ_STATUS_READ != value) &&
- (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case CANCEL_STATUS:
- if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
- (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case PRIORITY:
- if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case STATUS:
- if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case REPLY_CHARGING:
- if ((value < REPLY_CHARGING_REQUESTED)
- || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case MM_STATE:
- if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case RECOMMENDED_RETRIEVAL_MODE:
- if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case CONTENT_CLASS:
- if ((value < CONTENT_CLASS_TEXT)
- || (value > CONTENT_CLASS_CONTENT_RICH)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- case RETRIEVE_STATUS:
- // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
- if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
- (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
- value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
- } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
- (value <= RETRIEVE_STATUS_ERROR_END)) {
- value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
- } else if ((value < RETRIEVE_STATUS_OK) ||
- ((value > RETRIEVE_STATUS_OK) &&
- (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
- (value > RETRIEVE_STATUS_ERROR_END)) {
- value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
- }
- break;
- case STORE_STATUS:
- // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
- if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
- (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
- value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
- } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
- (value <= STORE_STATUS_ERROR_END)) {
- value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
- } else if ((value < STORE_STATUS_SUCCESS) ||
- ((value > STORE_STATUS_SUCCESS) &&
- (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
- (value > STORE_STATUS_ERROR_END)) {
- value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
- }
- break;
- case RESPONSE_STATUS:
- // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
- if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
- (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
- value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
- } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
- (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
- (value < RESPONSE_STATUS_OK) ||
- ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
- (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
- (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
- value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
- }
- break;
- case MMS_VERSION:
- if ((value < MMS_VERSION_1_0) || (value > MMS_VERSION_1_3)) {
- value = CURRENT_MMS_VERSION; // Current version is the default value.
- }
- break;
- case MESSAGE_TYPE:
- if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
- // Invalid value.
- throw new InvalidHeaderValueException("Invalid Octet value!");
- }
- break;
- default:
- // This header value should not be Octect.
- throw new RuntimeException("Invalid header field!");
- }
- mHeaderMap.put(field, value);
- }
-
- /**
- * Get TextString value by header field.
- *
- * @param field the field
- * @return the TextString value of the pdu header
- * with specified header field
- */
- protected byte[] getTextString(int field) {
- return (byte[]) mHeaderMap.get(field);
- }
-
- /**
- * Set TextString value to pdu header by header field.
- *
- * @param value the value
- * @param field the field
- * @return the TextString value of the pdu header
- * with specified header field
- * @throws NullPointerException if the value is null.
- */
- protected void setTextString(byte[] value, int field) {
- /**
- * Check whether this field can be set for specific
- * header and check validity of the field.
- */
- if (null == value) {
- throw new NullPointerException();
- }
-
- switch (field) {
- case TRANSACTION_ID:
- case REPLY_CHARGING_ID:
- case AUX_APPLIC_ID:
- case APPLIC_ID:
- case REPLY_APPLIC_ID:
- case MESSAGE_ID:
- case REPLACE_ID:
- case CANCEL_ID:
- case CONTENT_LOCATION:
- case MESSAGE_CLASS:
- case CONTENT_TYPE:
- break;
- default:
- // This header value should not be Text-String.
- throw new RuntimeException("Invalid header field!");
- }
- mHeaderMap.put(field, value);
- }
-
- /**
- * Get EncodedStringValue value by header field.
- *
- * @param field the field
- * @return the EncodedStringValue value of the pdu header
- * with specified header field
- */
- protected EncodedStringValue getEncodedStringValue(int field) {
- return (EncodedStringValue) mHeaderMap.get(field);
- }
-
- /**
- * Get TO, CC or BCC header value.
- *
- * @param field the field
- * @return the EncodeStringValue array of the pdu header
- * with specified header field
- */
- protected EncodedStringValue[] getEncodedStringValues(int field) {
- ArrayList<EncodedStringValue> list =
- (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
- if (null == list) {
- return null;
- }
- EncodedStringValue[] values = new EncodedStringValue[list.size()];
- return list.toArray(values);
- }
-
- /**
- * Set EncodedStringValue value to pdu header by header field.
- *
- * @param value the value
- * @param field the field
- * @return the EncodedStringValue value of the pdu header
- * with specified header field
- * @throws NullPointerException if the value is null.
- */
- protected void setEncodedStringValue(EncodedStringValue value, int field) {
- /**
- * Check whether this field can be set for specific
- * header and check validity of the field.
- */
- if (null == value) {
- throw new NullPointerException();
- }
-
- switch (field) {
- case SUBJECT:
- case RECOMMENDED_RETRIEVAL_MODE_TEXT:
- case RETRIEVE_TEXT:
- case STATUS_TEXT:
- case STORE_STATUS_TEXT:
- case RESPONSE_TEXT:
- case FROM:
- case PREVIOUSLY_SENT_BY:
- case MM_FLAGS:
- break;
- default:
- // This header value should not be Encoded-String-Value.
- throw new RuntimeException("Invalid header field!");
- }
-
- mHeaderMap.put(field, value);
- }
-
- /**
- * Set TO, CC or BCC header value.
- *
- * @param value the value
- * @param field the field
- * @return the EncodedStringValue value array of the pdu header
- * with specified header field
- * @throws NullPointerException if the value is null.
- */
- protected void setEncodedStringValues(EncodedStringValue[] value, int field) {
- /**
- * Check whether this field can be set for specific
- * header and check validity of the field.
- */
- if (null == value) {
- throw new NullPointerException();
- }
-
- switch (field) {
- case BCC:
- case CC:
- case TO:
- break;
- default:
- // This header value should not be Encoded-String-Value.
- throw new RuntimeException("Invalid header field!");
- }
-
- ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
- for (int i = 0; i < value.length; i++) {
- list.add(value[i]);
- }
- mHeaderMap.put(field, list);
- }
-
- /**
- * Append one EncodedStringValue to another.
- *
- * @param value the EncodedStringValue to append
- * @param field the field
- * @throws NullPointerException if the value is null.
- */
- protected void appendEncodedStringValue(EncodedStringValue value,
- int field) {
- if (null == value) {
- throw new NullPointerException();
- }
-
- switch (field) {
- case BCC:
- case CC:
- case TO:
- break;
- default:
- throw new RuntimeException("Invalid header field!");
- }
-
- ArrayList<EncodedStringValue> list =
- (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
- if (null == list) {
- list = new ArrayList<EncodedStringValue>();
- }
- list.add(value);
- mHeaderMap.put(field, list);
- }
-
- /**
- * Get LongInteger value by header field.
- *
- * @param field the field
- * @return the LongInteger value of the pdu header
- * with specified header field. if return -1, the
- * field is not existed in pdu header.
- */
- protected long getLongInteger(int field) {
- Long longInteger = (Long) mHeaderMap.get(field);
- if (null == longInteger) {
- return -1;
- }
-
- return longInteger.longValue();
- }
-
- /**
- * Set LongInteger value to pdu header by header field.
- *
- * @param value the value
- * @param field the field
- */
- protected void setLongInteger(long value, int field) {
- /**
- * Check whether this field can be set for specific
- * header and check validity of the field.
- */
- switch (field) {
- case DATE:
- case REPLY_CHARGING_SIZE:
- case MESSAGE_SIZE:
- case MESSAGE_COUNT:
- case START:
- case LIMIT:
- case DELIVERY_TIME:
- case EXPIRY:
- case REPLY_CHARGING_DEADLINE:
- case PREVIOUSLY_SENT_DATE:
- break;
- default:
- // This header value should not be LongInteger.
- throw new RuntimeException("Invalid header field!");
- }
- mHeaderMap.put(field, value);
- }
-
- public boolean hasHeader(int field) {
- return mHeaderMap.get(field, null) != null;
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/PduParser.java b/src/com/android/messaging/mmslib/pdu/PduParser.java
deleted file mode 100755
index e392fb5..0000000
--- a/src/com/android/messaging/mmslib/pdu/PduParser.java
+++ /dev/null
@@ -1,2044 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.messaging.mmslib.pdu;
-
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-import com.android.messaging.util.ContentType;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
-
-public class PduParser {
- /**
- * The log tag.
- */
- private static final String LOG_TAG = "PduParser";
-
- private static final boolean LOCAL_LOGV = false;
-
- /**
- * The next are WAP values defined in WSP specification.
- */
- private static final int QUOTE = 127;
-
- private static final int LENGTH_QUOTE = 31;
-
- private static final int TEXT_MIN = 32;
-
- private static final int TEXT_MAX = 127;
-
- private static final int SHORT_INTEGER_MAX = 127;
-
- private static final int SHORT_LENGTH_MAX = 30;
-
- private static final int LONG_INTEGER_LENGTH_MAX = 8;
-
- private static final int QUOTED_STRING_FLAG = 34;
-
- private static final int END_STRING_FLAG = 0x00;
-
- //The next two are used by the interface "parseWapString" to
- //distinguish Text-String and Quoted-String.
- private static final int TYPE_TEXT_STRING = 0;
-
- private static final int TYPE_QUOTED_STRING = 1;
-
- private static final int TYPE_TOKEN_STRING = 2;
-
- /**
- * Specify the part position.
- */
- private static final int THE_FIRST_PART = 0;
-
- private static final int THE_LAST_PART = 1;
-
- /**
- * The pdu data.
- */
- private ByteArrayInputStream mPduDataStream = null;
-
- /**
- * Store pdu headers
- */
- private PduHeaders mHeaders = null;
-
- /**
- * Store pdu parts.
- */
- private PduBody mBody = null;
-
- /**
- * Store the "type" parameter in "Content-Type" header field.
- */
- private static byte[] mTypeParam = null;
-
- /**
- * Store the "start" parameter in "Content-Type" header field.
- */
- private static byte[] mStartParam = null;
-
- /**
- * Whether to parse content-disposition part header
- */
- private final boolean mParseContentDisposition;
-
- /**
- * Constructor.
- *
- * @param pduDataStream pdu data to be parsed
- * @param parseContentDisposition whether to parse the Content-Disposition part header
- */
- public PduParser(byte[] pduDataStream, boolean parseContentDisposition) {
- mPduDataStream = new ByteArrayInputStream(pduDataStream);
- mParseContentDisposition = parseContentDisposition;
- }
-
- /**
- * Parse the pdu.
- *
- * @return the pdu structure if parsing successfully.
- * null if parsing error happened or mandatory fields are not set.
- */
- public GenericPdu parse() {
- if (mPduDataStream == null) {
- return null;
- }
-
- /* parse headers */
- mHeaders = parseHeaders(mPduDataStream);
- if (null == mHeaders) {
- // Parse headers failed.
- return null;
- }
-
- /* get the message type */
- int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
-
- /* check mandatory header fields */
- if (false == checkMandatoryHeader(mHeaders)) {
- log("check mandatory headers failed!");
- return null;
- }
- /*
- * Get retrieve status. If the header is not there, assuming it is OK status.
- * Some carriers may choose to not send this header.
- */
- int retrieveStatus = mHeaders.hasHeader(PduHeaders.RETRIEVE_STATUS) ?
- mHeaders.getOctet(PduHeaders.RETRIEVE_STATUS) : PduHeaders.RETRIEVE_STATUS_OK;
-
- if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
- (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType &&
- retrieveStatus == PduHeaders.RETRIEVE_STATUS_OK)) {
- /* need to parse the parts */
- mBody = parseParts(mPduDataStream);
- if (null == mBody) {
- // Parse parts failed.
- return null;
- }
- }
-
- switch (messageType) {
- case PduHeaders.MESSAGE_TYPE_SEND_REQ:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
- }
- SendReq sendReq = new SendReq(mHeaders, mBody);
- return sendReq;
- case PduHeaders.MESSAGE_TYPE_SEND_CONF:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
- }
- SendConf sendConf = new SendConf(mHeaders);
- return sendConf;
- case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
- }
- NotificationInd notificationInd =
- new NotificationInd(mHeaders);
- return notificationInd;
- case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
- }
- NotifyRespInd notifyRespInd =
- new NotifyRespInd(mHeaders);
- return notifyRespInd;
- case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
- }
- RetrieveConf retrieveConf =
- new RetrieveConf(mHeaders, mBody);
- if (retrieveStatus != PduHeaders.RETRIEVE_STATUS_OK) {
- // For failure only no need to check content type
- return retrieveConf;
- }
- byte[] contentType = retrieveConf.getContentType();
- if (null == contentType) {
- return null;
- }
- String ctTypeStr = new String(contentType);
- if (ctTypeStr.equals(ContentType.MMS_MULTIPART_MIXED)
- || ctTypeStr.equals(ContentType.MMS_MULTIPART_RELATED)
- || ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
- // The MMS content type must be "application/vnd.wap.multipart.mixed"
- // or "application/vnd.wap.multipart.related"
- // or "application/vnd.wap.multipart.alternative"
- return retrieveConf;
- } else if (ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
- // "application/vnd.wap.multipart.alternative"
- // should take only the first part.
- PduPart firstPart = mBody.getPart(0);
- mBody.removeAll();
- mBody.addPart(0, firstPart);
- return retrieveConf;
- }
- return null;
- case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
- }
- DeliveryInd deliveryInd =
- new DeliveryInd(mHeaders);
- return deliveryInd;
- case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
- }
- AcknowledgeInd acknowledgeInd =
- new AcknowledgeInd(mHeaders);
- return acknowledgeInd;
- case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
- }
- ReadOrigInd readOrigInd =
- new ReadOrigInd(mHeaders);
- return readOrigInd;
- case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
- }
- ReadRecInd readRecInd =
- new ReadRecInd(mHeaders);
- return readRecInd;
- default:
- log("Parser doesn't support this message type in this version!");
- return null;
- }
- }
-
- /**
- * Parse pdu headers.
- *
- * @param pduDataStream pdu data input stream
- * @return headers in PduHeaders structure, null when parse fail
- */
- protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream) {
- if (pduDataStream == null) {
- return null;
- }
- boolean keepParsing = true;
- PduHeaders headers = new PduHeaders();
-
- while (keepParsing && (pduDataStream.available() > 0)) {
- pduDataStream.mark(1);
- int headerField = extractByteValue(pduDataStream);
- /* parse custom text header */
- if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
- pduDataStream.reset();
- byte[] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
- }
- /* we should ignore it at the moment */
- continue;
- }
- switch (headerField) {
- case PduHeaders.MESSAGE_TYPE: {
- int messageType = extractByteValue(pduDataStream);
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType +
- " (" + Integer.toHexString(headerField) + ")");
- }
- switch (messageType) {
- // We don't support these kind of messages now.
- case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
- case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
- case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
- case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
- case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
- case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
- case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
- case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
- case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
- case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
- return null;
- }
- try {
- headers.setOctet(messageType, headerField);
- } catch (InvalidHeaderValueException e) {
- log("Set invalid Octet value: " + messageType +
- " into the header filed: " + headerField);
- return null;
- } catch (RuntimeException e) {
- log(headerField + "is not Octet header field!");
- return null;
- }
- break;
- }
- /* Octect value */
- case PduHeaders.REPORT_ALLOWED:
- case PduHeaders.ADAPTATION_ALLOWED:
- case PduHeaders.DELIVERY_REPORT:
- case PduHeaders.DRM_CONTENT:
- case PduHeaders.DISTRIBUTION_INDICATOR:
- case PduHeaders.QUOTAS:
- case PduHeaders.READ_REPORT:
- case PduHeaders.STORE:
- case PduHeaders.STORED:
- case PduHeaders.TOTALS:
- case PduHeaders.SENDER_VISIBILITY:
- case PduHeaders.READ_STATUS:
- case PduHeaders.CANCEL_STATUS:
- case PduHeaders.PRIORITY:
- case PduHeaders.STATUS:
- case PduHeaders.REPLY_CHARGING:
- case PduHeaders.MM_STATE:
- case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
- case PduHeaders.CONTENT_CLASS:
- case PduHeaders.RETRIEVE_STATUS:
- case PduHeaders.STORE_STATUS:
- /**
- * The following field has a different value when
- * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
- * For now we ignore this fact, since we do not support these PDUs
- */
- case PduHeaders.RESPONSE_STATUS: {
- int value = extractByteValue(pduDataStream);
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") Octect value: " +
- value);
- }
-
- try {
- headers.setOctet(value, headerField);
- } catch (InvalidHeaderValueException e) {
- log("Set invalid Octet value: " + value +
- " into the header filed: " + headerField);
- return null;
- } catch (RuntimeException e) {
- log(headerField + "is not Octet header field!");
- return null;
- }
- break;
- }
-
- /* Long-Integer */
- case PduHeaders.DATE:
- case PduHeaders.REPLY_CHARGING_SIZE:
- case PduHeaders.MESSAGE_SIZE: {
- try {
- long value = parseLongInteger(pduDataStream);
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") longint value: " +
- value);
- }
- headers.setLongInteger(value, headerField);
- } catch (RuntimeException e) {
- log(headerField + "is not Long-Integer header field!");
- return null;
- }
- break;
- }
-
- /* Integer-Value */
- case PduHeaders.MESSAGE_COUNT:
- case PduHeaders.START:
- case PduHeaders.LIMIT: {
- try {
- long value = parseIntegerValue(pduDataStream);
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") integer value: " +
- value);
- }
- headers.setLongInteger(value, headerField);
- } catch (RuntimeException e) {
- log(headerField + "is not Long-Integer header field!");
- return null;
- }
- break;
- }
-
- /* Text-String */
- case PduHeaders.TRANSACTION_ID:
- case PduHeaders.REPLY_CHARGING_ID:
- case PduHeaders.AUX_APPLIC_ID:
- case PduHeaders.APPLIC_ID:
- case PduHeaders.REPLY_APPLIC_ID:
- /**
- * The next three header fields are email addresses
- * as defined in RFC2822,
- * not including the characters "<" and ">"
- */
- case PduHeaders.MESSAGE_ID:
- case PduHeaders.REPLACE_ID:
- case PduHeaders.CANCEL_ID:
- /**
- * The following field has a different value when
- * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
- * For now we ignore this fact, since we do not support these PDUs
- */
- case PduHeaders.CONTENT_LOCATION: {
- byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- if (null != value) {
- try {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") string value: "
- +
- new String(value));
- }
- headers.setTextString(value, headerField);
- } catch (NullPointerException e) {
- log("null pointer error!");
- } catch (RuntimeException e) {
- log(headerField + "is not Text-String header field!");
- return null;
- }
- }
- break;
- }
-
- /* Encoded-string-value */
- case PduHeaders.SUBJECT:
- case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
- case PduHeaders.RETRIEVE_TEXT:
- case PduHeaders.STATUS_TEXT:
- case PduHeaders.STORE_STATUS_TEXT:
- /* the next one is not support
- * M-Mbox-Delete.conf and M-Delete.conf now */
- case PduHeaders.RESPONSE_TEXT: {
- EncodedStringValue value =
- parseEncodedStringValue(pduDataStream);
- if (null != value) {
- try {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField)
- + ") encoded string: " +
- value.getString());
- }
- headers.setEncodedStringValue(value, headerField);
- } catch (NullPointerException e) {
- log("null pointer error!");
- } catch (RuntimeException e) {
- log(headerField + "is not Encoded-String-Value header field!");
- return null;
- }
- }
- break;
- }
-
- /* Addressing model */
- case PduHeaders.BCC:
- case PduHeaders.CC:
- case PduHeaders.TO: {
- EncodedStringValue value =
- parseEncodedStringValue(pduDataStream);
- if (null != value) {
- byte[] address = value.getTextString();
- if (null != address) {
- String str = new String(address);
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField
- + " value: " + str);
- }
- int endIndex = str.indexOf("/");
- if (endIndex > 0) {
- str = str.substring(0, endIndex);
- }
- try {
- value.setTextString(str.getBytes());
- } catch (NullPointerException e) {
- log("null pointer error!");
- return null;
- }
- }
-
- try {
- headers.appendEncodedStringValue(value, headerField);
- } catch (NullPointerException e) {
- log("null pointer error!");
- } catch (RuntimeException e) {
- log(headerField + "is not Encoded-String-Value header field!");
- return null;
- }
- }
- break;
- }
-
- /* Value-length
- * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
- case PduHeaders.DELIVERY_TIME:
- case PduHeaders.EXPIRY:
- case PduHeaders.REPLY_CHARGING_DEADLINE: {
- /* parse Value-length */
- parseValueLength(pduDataStream);
-
- /* Absolute-token or Relative-token */
- int token = extractByteValue(pduDataStream);
-
- /* Date-value or Delta-seconds-value */
- long timeValue;
- try {
- timeValue = parseLongInteger(pduDataStream);
- } catch (RuntimeException e) {
- log(headerField + "is not Long-Integer header field!");
- return null;
- }
- if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
- /* need to convert the Delta-seconds-value
- * into Date-value */
- timeValue = System.currentTimeMillis() / 1000 + timeValue;
- }
-
- try {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") time value: " +
- timeValue);
- }
- headers.setLongInteger(timeValue, headerField);
- } catch (RuntimeException e) {
- log(headerField + "is not Long-Integer header field!");
- return null;
- }
- break;
- }
-
- case PduHeaders.FROM: {
- /* From-value =
- * Value-length
- * (Address-present-token Encoded-string-value | Insert-address-token)
- */
- EncodedStringValue from = null;
- parseValueLength(pduDataStream); /* parse value-length */
-
- /* Address-present-token or Insert-address-token */
- int fromToken = extractByteValue(pduDataStream);
-
- /* Address-present-token or Insert-address-token */
- if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
- /* Encoded-string-value */
- from = parseEncodedStringValue(pduDataStream);
- if (null != from) {
- byte[] address = from.getTextString();
- if (null != address) {
- String str = new String(address);
- int endIndex = str.indexOf("/");
- if (endIndex > 0) {
- str = str.substring(0, endIndex);
- }
- try {
- from.setTextString(str.getBytes());
- } catch (NullPointerException e) {
- log("null pointer error!");
- return null;
- }
- }
- }
- } else {
- try {
- from = new EncodedStringValue(
- PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
- } catch (NullPointerException e) {
- log(headerField + "is not Encoded-String-Value header field!");
- return null;
- }
- }
-
- try {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") from address: " +
- from.getString());
- }
- headers.setEncodedStringValue(from, PduHeaders.FROM);
- } catch (NullPointerException e) {
- log("null pointer error!");
- } catch (RuntimeException e) {
- log(headerField + "is not Encoded-String-Value header field!");
- return null;
- }
- break;
- }
-
- case PduHeaders.MESSAGE_CLASS: {
- /* Message-class-value = Class-identifier | Token-text */
- pduDataStream.mark(1);
- int messageClass = extractByteValue(pduDataStream);
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") MESSAGE_CLASS: " +
- messageClass);
- }
-
- if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
- /* Class-identifier */
- try {
- if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
- headers.setTextString(
- PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
- PduHeaders.MESSAGE_CLASS);
- } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
- headers.setTextString(
- PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
- PduHeaders.MESSAGE_CLASS);
- } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
- headers.setTextString(
- PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
- PduHeaders.MESSAGE_CLASS);
- } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
- headers.setTextString(
- PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
- PduHeaders.MESSAGE_CLASS);
- }
- } catch (NullPointerException e) {
- log("null pointer error!");
- } catch (RuntimeException e) {
- log(headerField + "is not Text-String header field!");
- return null;
- }
- } else {
- /* Token-text */
- pduDataStream.reset();
- byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- if (null != messageClassString) {
- try {
- headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
- } catch (NullPointerException e) {
- log("null pointer error!");
- } catch (RuntimeException e) {
- log(headerField + "is not Text-String header field!");
- return null;
- }
- }
- }
- break;
- }
-
- case PduHeaders.MMS_VERSION: {
- int version = parseShortInteger(pduDataStream);
-
- try {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") MMS_VERSION: " +
- version);
- }
- headers.setOctet(version, PduHeaders.MMS_VERSION);
- } catch (InvalidHeaderValueException e) {
- log("Set invalid Octet value: " + version +
- " into the header filed: " + headerField);
- return null;
- } catch (RuntimeException e) {
- log(headerField + "is not Octet header field!");
- return null;
- }
- break;
- }
-
- case PduHeaders.PREVIOUSLY_SENT_BY: {
- /* Previously-sent-by-value =
- * Value-length Forwarded-count-value Encoded-string-value */
- /* parse value-length */
- parseValueLength(pduDataStream);
-
- /* parse Forwarded-count-value */
- try {
- parseIntegerValue(pduDataStream);
- } catch (RuntimeException e) {
- log(headerField + " is not Integer-Value");
- return null;
- }
-
- /* parse Encoded-string-value */
- EncodedStringValue previouslySentBy =
- parseEncodedStringValue(pduDataStream);
- if (null != previouslySentBy) {
- try {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) +
- ") PREVIOUSLY_SENT_BY: " + previouslySentBy.getString());
- }
- headers.setEncodedStringValue(previouslySentBy,
- PduHeaders.PREVIOUSLY_SENT_BY);
- } catch (NullPointerException e) {
- log("null pointer error!");
- } catch (RuntimeException e) {
- log(headerField + "is not Encoded-String-Value header field!");
- return null;
- }
- }
- break;
- }
-
- case PduHeaders.PREVIOUSLY_SENT_DATE: {
- /* Previously-sent-date-value =
- * Value-length Forwarded-count-value Date-value */
- /* parse value-length */
- parseValueLength(pduDataStream);
-
- /* parse Forwarded-count-value */
- try {
- parseIntegerValue(pduDataStream);
- } catch (RuntimeException e) {
- log(headerField + " is not Integer-Value");
- return null;
- }
-
- /* Date-value */
- try {
- long previouslySentDate = parseLongInteger(pduDataStream);
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) +
- ") PREVIOUSLY_SENT_DATE: " + previouslySentDate);
- }
- headers.setLongInteger(previouslySentDate,
- PduHeaders.PREVIOUSLY_SENT_DATE);
- } catch (RuntimeException e) {
- log(headerField + "is not Long-Integer header field!");
- return null;
- }
- break;
- }
-
- case PduHeaders.MM_FLAGS: {
- /* MM-flags-value =
- * Value-length
- * ( Add-token | Remove-token | Filter-token )
- * Encoded-string-value
- */
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") MM_FLAGS: " +
- " NOT REALLY SUPPORTED");
- }
-
- /* parse Value-length */
- parseValueLength(pduDataStream);
-
- /* Add-token | Remove-token | Filter-token */
- extractByteValue(pduDataStream);
-
- /* Encoded-string-value */
- parseEncodedStringValue(pduDataStream);
-
- /* not store this header filed in "headers",
- * because now PduHeaders doesn't support it */
- break;
- }
-
- /* Value-length
- * (Message-total-token | Size-total-token) Integer-Value */
- case PduHeaders.MBOX_TOTALS:
- case PduHeaders.MBOX_QUOTAS: {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") MBOX_");
- }
- /* Value-length */
- parseValueLength(pduDataStream);
-
- /* Message-total-token | Size-total-token */
- extractByteValue(pduDataStream);
-
- /*Integer-Value*/
- try {
- parseIntegerValue(pduDataStream);
- } catch (RuntimeException e) {
- log(headerField + " is not Integer-Value");
- return null;
- }
-
- /* not store these headers filed in "headers",
- because now PduHeaders doesn't support them */
- break;
- }
-
- case PduHeaders.ELEMENT_DESCRIPTOR: {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") ELEMENT_DESCRIPTOR");
- }
- parseContentType(pduDataStream, null);
-
- /* not store this header filed in "headers",
- because now PduHeaders doesn't support it */
- break;
- }
-
- case PduHeaders.CONTENT_TYPE: {
- SparseArray<Object> map = new SparseArray<Object>();
- byte[] contentType =
- parseContentType(pduDataStream, map);
-
- if (null != contentType) {
- try {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
- " (" + Integer.toHexString(headerField) + ") CONTENT_TYPE: "
- + Arrays.toString(contentType));
- }
- headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
- } catch (NullPointerException e) {
- log("null pointer error!");
- } catch (RuntimeException e) {
- log(headerField + "is not Text-String header field!");
- return null;
- }
- }
-
- /* get start parameter */
- mStartParam = (byte[]) map.get(PduPart.P_START);
-
- /* get charset parameter */
- mTypeParam = (byte[]) map.get(PduPart.P_TYPE);
-
- keepParsing = false;
- break;
- }
-
- case PduHeaders.CONTENT:
- case PduHeaders.ADDITIONAL_HEADERS:
- case PduHeaders.ATTRIBUTES:
- default: {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField +
- " (" + Integer.toHexString(headerField) + ")");
- }
- log("Unknown header");
- }
- }
- }
-
- return headers;
- }
-
- /**
- * Parse pdu parts.
- *
- * @param pduDataStream pdu data input stream
- * @return parts in PduBody structure
- */
- protected PduBody parseParts(ByteArrayInputStream pduDataStream) {
- if (pduDataStream == null) {
- return null;
- }
-
- int count = parseUnsignedInt(pduDataStream); // get the number of parts
- PduBody body = new PduBody();
-
- for (int i = 0; i < count; i++) {
- int headerLength = parseUnsignedInt(pduDataStream);
- int dataLength = parseUnsignedInt(pduDataStream);
- PduPart part = new PduPart();
- int startPos = pduDataStream.available();
- if (startPos <= 0) {
- // Invalid part.
- return null;
- }
-
- /* parse part's content-type */
- SparseArray<Object> map = new SparseArray<Object>();
- byte[] contentType = parseContentType(pduDataStream, map);
- if (null != contentType) {
- part.setContentType(contentType);
- } else {
- part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
- }
-
- /* get name parameter */
- byte[] name = (byte[]) map.get(PduPart.P_NAME);
- if (null != name) {
- part.setName(name);
- }
-
- /* get charset parameter */
- Integer charset = (Integer) map.get(PduPart.P_CHARSET);
- if (null != charset) {
- part.setCharset(charset);
- }
-
- /* parse part's headers */
- int endPos = pduDataStream.available();
- int partHeaderLen = headerLength - (startPos - endPos);
- if (partHeaderLen > 0) {
- if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
- // Parse part header faild.
- return null;
- }
- } else if (partHeaderLen < 0) {
- // Invalid length of content-type.
- return null;
- }
-
- /* TODO: check content-id, name, filename and content location,
- * if not set anyone of them, generate a default content-location
- */
- if ((null == part.getContentLocation())
- && (null == part.getName())
- && (null == part.getFilename())
- && (null == part.getContentId())) {
- part.setContentLocation(Long.toOctalString(
- System.currentTimeMillis()).getBytes());
- }
-
- /* get part's data */
- if (dataLength > 0) {
- byte[] partData = new byte[dataLength];
- String partContentType = new String(part.getContentType());
- pduDataStream.read(partData, 0, dataLength);
- if (partContentType.equalsIgnoreCase(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
- // parse "multipart/vnd.wap.multipart.alternative".
- PduBody childBody = parseParts(new ByteArrayInputStream(partData));
- // take the first part of children.
- part = childBody.getPart(0);
- } else {
- // Check Content-Transfer-Encoding.
- byte[] partDataEncoding = part.getContentTransferEncoding();
- if (null != partDataEncoding) {
- String encoding = new String(partDataEncoding);
- if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
- // Decode "base64" into "binary".
- partData = Base64.decodeBase64(partData);
- } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
- // Decode "quoted-printable" into "binary".
- partData = QuotedPrintable.decodeQuotedPrintable(partData);
- } else {
- // "binary" is the default encoding.
- }
- }
- if (null == partData) {
- log("Decode part data error!");
- return null;
- }
- part.setData(partData);
- }
- }
-
- /* add this part to body */
- if (THE_FIRST_PART == checkPartPosition(part)) {
- /* this is the first part */
- body.addPart(0, part);
- } else {
- /* add the part to the end */
- body.addPart(part);
- }
- }
-
- return body;
- }
-
- /**
- * Log status.
- *
- * @param text log information
- */
- private static void log(String text) {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, text);
- }
- }
-
- /**
- * Parse unsigned integer.
- *
- * @param pduDataStream pdu data input stream
- * @return the integer, -1 when failed
- */
- protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
- /**
- * From wap-230-wsp-20010705-a.pdf
- * The maximum size of a uintvar is 32 bits.
- * So it will be encoded in no more than 5 octets.
- */
- assert (null != pduDataStream);
- int result = 0;
- int temp = pduDataStream.read();
- if (temp == -1) {
- return temp;
- }
-
- while ((temp & 0x80) != 0) {
- result = result << 7;
- result |= temp & 0x7F;
- temp = pduDataStream.read();
- if (temp == -1) {
- return temp;
- }
- }
-
- result = result << 7;
- result |= temp & 0x7F;
-
- return result;
- }
-
- /**
- * Parse value length.
- *
- * @param pduDataStream pdu data input stream
- * @return the integer
- */
- protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
- /**
- * From wap-230-wsp-20010705-a.pdf
- * Value-length = Short-length | (Length-quote Length)
- * Short-length = <Any octet 0-30>
- * Length-quote = <Octet 31>
- * Length = Uintvar-integer
- * Uintvar-integer = 1*5 OCTET
- */
- assert (null != pduDataStream);
- int temp = pduDataStream.read();
- assert (-1 != temp);
- int first = temp & 0xFF;
-
- if (first <= SHORT_LENGTH_MAX) {
- return first;
- } else if (first == LENGTH_QUOTE) {
- return parseUnsignedInt(pduDataStream);
- }
-
- throw new RuntimeException("Value length > LENGTH_QUOTE!");
- }
-
- /**
- * Parse encoded string value.
- *
- * @param pduDataStream pdu data input stream
- * @return the EncodedStringValue
- */
- protected static EncodedStringValue parseEncodedStringValue(
- ByteArrayInputStream pduDataStream) {
- /**
- * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
- * Encoded-string-value = Text-string | Value-length Char-set Text-string
- */
- assert (null != pduDataStream);
- pduDataStream.mark(1);
- EncodedStringValue returnValue = null;
- int charset = 0;
- int temp = pduDataStream.read();
- assert (-1 != temp);
- int first = temp & 0xFF;
- if (first == 0) {
- return null; // Blank subject, bail.
- }
-
- pduDataStream.reset();
- if (first < TEXT_MIN) {
- parseValueLength(pduDataStream);
-
- charset = parseShortInteger(pduDataStream); //get the "Charset"
- }
-
- byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-
- try {
- if (0 != charset) {
- returnValue = new EncodedStringValue(charset, textString);
- } else {
- returnValue = new EncodedStringValue(textString);
- }
- } catch (Exception e) {
- return null;
- }
-
- return returnValue;
- }
-
- /**
- * Parse Text-String or Quoted-String.
- *
- * @param pduDataStream pdu data input stream
- * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
- * @return the string without End-of-string in byte array
- */
- protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
- int stringType) {
- assert (null != pduDataStream);
- /**
- * From wap-230-wsp-20010705-a.pdf
- * Text-string = [Quote] *TEXT End-of-string
- * If the first character in the TEXT is in the range of 128-255,
- * a Quote character must precede it.
- * Otherwise the Quote character must be omitted.
- * The Quote is not part of the contents.
- * Quote = <Octet 127>
- * End-of-string = <Octet 0>
- *
- * Quoted-string = <Octet 34> *TEXT End-of-string
- *
- * Token-text = Token End-of-string
- */
-
- // Mark supposed beginning of Text-string
- // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
- pduDataStream.mark(1);
-
- // Check first char
- int temp = pduDataStream.read();
- assert (-1 != temp);
- if ((TYPE_QUOTED_STRING == stringType) &&
- (QUOTED_STRING_FLAG == temp)) {
- // Mark again if QUOTED_STRING_FLAG and ignore it
- pduDataStream.mark(1);
- } else if ((TYPE_TEXT_STRING == stringType) &&
- (QUOTE == temp)) {
- // Mark again if QUOTE and ignore it
- pduDataStream.mark(1);
- } else {
- // Otherwise go back to origin
- pduDataStream.reset();
- }
-
- // We are now definitely at the beginning of string
- /**
- * Return *TOKEN or *TEXT (Text-String without QUOTE,
- * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
- */
- return getWapString(pduDataStream, stringType);
- }
-
- /**
- * Check TOKEN data defined in RFC2616.
- *
- * @param ch checking data
- * @return true when ch is TOKEN, false when ch is not TOKEN
- */
- protected static boolean isTokenCharacter(int ch) {
- /**
- * Token = 1*<any CHAR except CTLs or separators>
- * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
- * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
- * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
- * | "{"(123) | "}"(125) | SP(32) | HT(9)
- * CHAR = <any US-ASCII character (octets 0 - 127)>
- * CTL = <any US-ASCII control character
- * (octets 0 - 31) and DEL (127)>
- * SP = <US-ASCII SP, space (32)>
- * HT = <US-ASCII HT, horizontal-tab (9)>
- */
- if ((ch < 33) || (ch > 126)) {
- return false;
- }
-
- switch (ch) {
- case '"': /* '"' */
- case '(': /* '(' */
- case ')': /* ')' */
- case ',': /* ',' */
- case '/': /* '/' */
- case ':': /* ':' */
- case ';': /* ';' */
- case '<': /* '<' */
- case '=': /* '=' */
- case '>': /* '>' */
- case '?': /* '?' */
- case '@': /* '@' */
- case '[': /* '[' */
- case '\\': /* '\' */
- case ']': /* ']' */
- case '{': /* '{' */
- case '}': /* '}' */
- return false;
- }
-
- return true;
- }
-
- /**
- * Check TEXT data defined in RFC2616.
- *
- * @param ch checking data
- * @return true when ch is TEXT, false when ch is not TEXT
- */
- protected static boolean isText(int ch) {
- /**
- * TEXT = <any OCTET except CTLs,
- * but including LWS>
- * CTL = <any US-ASCII control character
- * (octets 0 - 31) and DEL (127)>
- * LWS = [CRLF] 1*( SP | HT )
- * CRLF = CR LF
- * CR = <US-ASCII CR, carriage return (13)>
- * LF = <US-ASCII LF, linefeed (10)>
- */
- if (((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
- return true;
- }
-
- switch (ch) {
- case '\t': /* '\t' */
- case '\n': /* '\n' */
- case '\r': /* '\r' */
- return true;
- }
-
- return false;
- }
-
- protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
- int stringType) {
- assert (null != pduDataStream);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- int temp = pduDataStream.read();
- assert (-1 != temp);
- while ((-1 != temp) && ('\0' != temp)) {
- // check each of the character
- if (stringType == TYPE_TOKEN_STRING) {
- if (isTokenCharacter(temp)) {
- out.write(temp);
- }
- } else {
- if (isText(temp)) {
- out.write(temp);
- }
- }
-
- temp = pduDataStream.read();
- assert (-1 != temp);
- }
-
- if (out.size() > 0) {
- return out.toByteArray();
- }
-
- return null;
- }
-
- /**
- * Extract a byte value from the input stream.
- *
- * @param pduDataStream pdu data input stream
- * @return the byte
- */
- protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
- assert (null != pduDataStream);
- int temp = pduDataStream.read();
- assert (-1 != temp);
- return temp & 0xFF;
- }
-
- /**
- * Parse Short-Integer.
- *
- * @param pduDataStream pdu data input stream
- * @return the byte
- */
- protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
- /**
- * From wap-230-wsp-20010705-a.pdf
- * Short-integer = OCTET
- * Integers in range 0-127 shall be encoded as a one
- * octet value with the most significant bit set to one (1xxx xxxx)
- * and with the value in the remaining least significant bits.
- */
- assert (null != pduDataStream);
- int temp = pduDataStream.read();
- assert (-1 != temp);
- return temp & 0x7F;
- }
-
- /**
- * Parse Long-Integer.
- *
- * @param pduDataStream pdu data input stream
- * @return long integer
- */
- protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
- /**
- * From wap-230-wsp-20010705-a.pdf
- * Long-integer = Short-length Multi-octet-integer
- * The Short-length indicates the length of the Multi-octet-integer
- * Multi-octet-integer = 1*30 OCTET
- * The content octets shall be an unsigned integer value
- * with the most significant octet encoded first (big-endian representation).
- * The minimum number of octets must be used to encode the value.
- * Short-length = <Any octet 0-30>
- */
- assert (null != pduDataStream);
- int temp = pduDataStream.read();
- assert (-1 != temp);
- int count = temp & 0xFF;
-
- if (count > LONG_INTEGER_LENGTH_MAX) {
- throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
- }
-
- long result = 0;
-
- for (int i = 0; i < count; i++) {
- temp = pduDataStream.read();
- assert (-1 != temp);
- result <<= 8;
- result += (temp & 0xFF);
- }
-
- return result;
- }
-
- /**
- * Parse Integer-Value.
- *
- * @param pduDataStream pdu data input stream
- * @return long integer
- */
- protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
- /**
- * From wap-230-wsp-20010705-a.pdf
- * Integer-Value = Short-integer | Long-integer
- */
- assert (null != pduDataStream);
- pduDataStream.mark(1);
- int temp = pduDataStream.read();
- assert (-1 != temp);
- pduDataStream.reset();
- if (temp > SHORT_INTEGER_MAX) {
- return parseShortInteger(pduDataStream);
- } else {
- return parseLongInteger(pduDataStream);
- }
- }
-
- /**
- * To skip length of the wap value.
- *
- * @param pduDataStream pdu data input stream
- * @param length area size
- * @return the values in this area
- */
- protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
- assert (null != pduDataStream);
- byte[] area = new byte[length];
- int readLen = pduDataStream.read(area, 0, length);
- if (readLen < length) { //The actually read length is lower than the length
- return -1;
- } else {
- return readLen;
- }
- }
-
- /**
- * Parse content type parameters. For now we just support
- * four parameters used in mms: "type", "start", "name", "charset".
- *
- * @param pduDataStream pdu data input stream
- * @param map to store parameters of Content-Type field
- * @param length length of all the parameters
- */
- protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
- SparseArray<Object> map, Integer length) {
- /**
- * From wap-230-wsp-20010705-a.pdf
- * Parameter = Typed-parameter | Untyped-parameter
- * Typed-parameter = Well-known-parameter-token Typed-value
- * the actual expected type of the value is implied by the well-known parameter
- * Well-known-parameter-token = Integer-value
- * the code values used for parameters are specified in the Assigned Numbers appendix
- * Typed-value = Compact-value | Text-value
- * In addition to the expected type, there may be no value.
- * If the value cannot be encoded using the expected type, it shall be encoded as text.
- * Compact-value = Integer-value |
- * Date-value | Delta-seconds-value | Q-value | Version-value |
- * Uri-value
- * Untyped-parameter = Token-text Untyped-value
- * the type of the value is unknown, but it shall be encoded as an integer,
- * if that is possible.
- * Untyped-value = Integer-value | Text-value
- */
- assert (null != pduDataStream);
- assert (length > 0);
-
- int startPos = pduDataStream.available();
- int tempPos = 0;
- int lastLen = length;
- while (0 < lastLen) {
- int param = pduDataStream.read();
- assert (-1 != param);
- lastLen--;
-
- switch (param) {
- /**
- * From rfc2387, chapter 3.1
- * The type parameter must be specified and its value is the MIME media
- * type of the "root" body part. It permits a MIME user agent to
- * determine the content-type without reference to the enclosed body
- * part. If the value of the type parameter and the root body part's
- * content-type differ then the User Agent's behavior is undefined.
- *
- * From wap-230-wsp-20010705-a.pdf
- * type = Constrained-encoding
- * Constrained-encoding = Extension-Media | Short-integer
- * Extension-media = *TEXT End-of-string
- */
- case PduPart.P_TYPE:
- case PduPart.P_CT_MR_TYPE:
- pduDataStream.mark(1);
- int first = extractByteValue(pduDataStream);
- pduDataStream.reset();
- if (first > TEXT_MAX) {
- // Short-integer (well-known type)
- int index = parseShortInteger(pduDataStream);
-
- if (index < PduContentTypes.contentTypes.length) {
- byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
- map.put(PduPart.P_TYPE, type);
- } else {
- //not support this type, ignore it.
- }
- } else {
- // Text-String (extension-media)
- byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- if ((null != type) && (null != map)) {
- map.put(PduPart.P_TYPE, type);
- }
- }
-
- tempPos = pduDataStream.available();
- lastLen = length - (startPos - tempPos);
- break;
-
- /**
- * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
- * Start Parameter Referring to Presentation
- *
- * From rfc2387, chapter 3.2
- * The start parameter, if given, is the content-ID of the compound
- * object's "root". If not present the "root" is the first body part in
- * the Multipart/Related entity. The "root" is the element the
- * applications processes first.
- *
- * From wap-230-wsp-20010705-a.pdf
- * start = Text-String
- */
- case PduPart.P_START:
- case PduPart.P_DEP_START:
- byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- if ((null != start) && (null != map)) {
- map.put(PduPart.P_START, start);
- }
-
- tempPos = pduDataStream.available();
- lastLen = length - (startPos - tempPos);
- break;
-
- /**
- * From oma-ts-mms-conf-v1_3.pdf
- * In creation, the character set SHALL be either us-ascii
- * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
- * In retrieval, both us-ascii and utf-8 SHALL be supported.
- *
- * From wap-230-wsp-20010705-a.pdf
- * charset = Well-known-charset|Text-String
- * Well-known-charset = Any-charset | Integer-value
- * Both are encoded using values from Character Set
- * Assignments table in Assigned Numbers
- * Any-charset = <Octet 128>
- * Equivalent to the special RFC2616 charset value "*"
- */
- case PduPart.P_CHARSET:
- pduDataStream.mark(1);
- int firstValue = extractByteValue(pduDataStream);
- pduDataStream.reset();
- //Check first char
- if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
- (END_STRING_FLAG == firstValue)) {
- //Text-String (extension-charset)
- byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- try {
- int charsetInt = CharacterSets.getMibEnumValue(
- new String(charsetStr));
- map.put(PduPart.P_CHARSET, charsetInt);
- } catch (UnsupportedEncodingException e) {
- // Not a well-known charset, use "*".
- Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
- map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
- }
- } else {
- //Well-known-charset
- int charset = (int) parseIntegerValue(pduDataStream);
- if (map != null) {
- map.put(PduPart.P_CHARSET, charset);
- }
- }
-
- tempPos = pduDataStream.available();
- lastLen = length - (startPos - tempPos);
- break;
-
- /**
- * From oma-ts-mms-conf-v1_3.pdf
- * A name for multipart object SHALL be encoded using name-parameter
- * for Content-Type header in WSP multipart headers.
- *
- * From wap-230-wsp-20010705-a.pdf
- * name = Text-String
- */
- case PduPart.P_DEP_NAME:
- case PduPart.P_NAME:
- byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- if ((null != name) && (null != map)) {
- map.put(PduPart.P_NAME, name);
- }
-
- tempPos = pduDataStream.available();
- lastLen = length - (startPos - tempPos);
- break;
- default:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "Not supported Content-Type parameter");
- }
- if (-1 == skipWapValue(pduDataStream, lastLen)) {
- Log.e(LOG_TAG, "Corrupt Content-Type");
- } else {
- lastLen = 0;
- }
- break;
- }
- }
-
- if (0 != lastLen) {
- Log.e(LOG_TAG, "Corrupt Content-Type");
- }
- }
-
- /**
- * Parse content type.
- *
- * @param pduDataStream pdu data input stream
- * @param map to store parameters in Content-Type header field
- * @return Content-Type value
- */
- protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
- SparseArray<Object> map) {
- /**
- * From wap-230-wsp-20010705-a.pdf
- * Content-type-value = Constrained-media | Content-general-form
- * Content-general-form = Value-length Media-type
- * Media-type = (Well-known-media | Extension-Media) *(Parameter)
- */
- assert (null != pduDataStream);
-
- byte[] contentType = null;
- pduDataStream.mark(1);
- int temp = pduDataStream.read();
- assert (-1 != temp);
- pduDataStream.reset();
-
- int cur = (temp & 0xFF);
-
- if (cur < TEXT_MIN) {
- int length = parseValueLength(pduDataStream);
- int startPos = pduDataStream.available();
- pduDataStream.mark(1);
- temp = pduDataStream.read();
- assert (-1 != temp);
- pduDataStream.reset();
- int first = (temp & 0xFF);
-
- if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
- contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- } else if (first > TEXT_MAX) {
- int index = parseShortInteger(pduDataStream);
-
- if (index < PduContentTypes.contentTypes.length) { //well-known type
- contentType = (PduContentTypes.contentTypes[index]).getBytes();
- } else {
- pduDataStream.reset();
- contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- }
- } else {
- Log.e(LOG_TAG, "Corrupt content-type");
- return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
- }
-
- int endPos = pduDataStream.available();
- int parameterLen = length - (startPos - endPos);
- if (parameterLen > 0) {//have parameters
- parseContentTypeParams(pduDataStream, map, parameterLen);
- }
-
- if (parameterLen < 0) {
- Log.e(LOG_TAG, "Corrupt MMS message");
- return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
- }
- } else if (cur <= TEXT_MAX) {
- contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- } else {
- contentType =
- (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
- }
-
- return contentType;
- }
-
- /**
- * Parse part's headers.
- *
- * @param pduDataStream pdu data input stream
- * @param part to store the header informations of the part
- * @param length length of the headers
- * @return true if parse successfully, false otherwise
- */
- protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
- PduPart part, int length) {
- assert (null != pduDataStream);
- assert (null != part);
- assert (length > 0);
-
- /**
- * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
- * A name for multipart object SHALL be encoded using name-parameter
- * for Content-Type header in WSP multipart headers.
- * In decoding, name-parameter of Content-Type SHALL be used if available.
- * If name-parameter of Content-Type is not available,
- * filename parameter of Content-Disposition header SHALL be used if available.
- * If neither name-parameter of Content-Type header nor filename parameter
- * of Content-Disposition header is available,
- * Content-Location header SHALL be used if available.
- *
- * Within SMIL part the reference to the media object parts SHALL use
- * either Content-ID or Content-Location mechanism [RFC2557]
- * and the corresponding WSP part headers in media object parts
- * contain the corresponding definitions.
- */
- int startPos = pduDataStream.available();
- int tempPos = 0;
- int lastLen = length;
- while (0 < lastLen) {
- int header = pduDataStream.read();
- assert (-1 != header);
- lastLen--;
-
- if (header > TEXT_MAX) {
- // Number assigned headers.
- switch (header) {
- case PduPart.P_CONTENT_LOCATION:
- /**
- * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
- * Content-location-value = Uri-value
- */
- byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- if (null != contentLocation) {
- part.setContentLocation(contentLocation);
- }
-
- tempPos = pduDataStream.available();
- lastLen = length - (startPos - tempPos);
- break;
- case PduPart.P_CONTENT_ID:
- /**
- * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
- * Content-ID-value = Quoted-string
- */
- byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
- if (null != contentId) {
- part.setContentId(contentId);
- }
-
- tempPos = pduDataStream.available();
- lastLen = length - (startPos - tempPos);
- break;
- case PduPart.P_DEP_CONTENT_DISPOSITION:
- case PduPart.P_CONTENT_DISPOSITION:
- /*
- * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
- * Content-disposition-value = Value-length Disposition *(Parameter)
- * Disposition = Form-data | Attachment | Inline | Token-text
- * Form-data = <Octet 128>
- * Attachment = <Octet 129>
- * Inline = <Octet 130>
- *
- * some carrier mmsc servers do not support content_disposition
- * field correctly
- */
- if (mParseContentDisposition) {
- int len = parseValueLength(pduDataStream);
- pduDataStream.mark(1);
- int thisStartPos = pduDataStream.available();
- int thisEndPos = 0;
- int value = pduDataStream.read();
-
- if (value == PduPart.P_DISPOSITION_FROM_DATA) {
- part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
- } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
- part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
- } else if (value == PduPart.P_DISPOSITION_INLINE) {
- part.setContentDisposition(PduPart.DISPOSITION_INLINE);
- } else {
- pduDataStream.reset();
- /* Token-text */
- part.setContentDisposition(parseWapString(pduDataStream
- , TYPE_TEXT_STRING));
- }
-
- /* get filename parameter and skip other parameters */
- thisEndPos = pduDataStream.available();
- if (thisStartPos - thisEndPos < len) {
- value = pduDataStream.read();
- if (value == PduPart.P_FILENAME) { //filename is text-string
- part.setFilename(parseWapString(pduDataStream
- , TYPE_TEXT_STRING));
- }
-
- /* skip other parameters */
- thisEndPos = pduDataStream.available();
- if (thisStartPos - thisEndPos < len) {
- int last = len - (thisStartPos - thisEndPos);
- byte[] temp = new byte[last];
- pduDataStream.read(temp, 0, last);
- }
- }
-
- tempPos = pduDataStream.available();
- lastLen = length - (startPos - tempPos);
- }
- break;
- default:
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "Not supported Part headers: " + header);
- }
- if (-1 == skipWapValue(pduDataStream, lastLen)) {
- Log.e(LOG_TAG, "Corrupt Part headers");
- return false;
- }
- lastLen = 0;
- break;
- }
- } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
- // Not assigned header.
- byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
- byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-
- // Check the header whether it is "Content-Transfer-Encoding".
- if (true ==
- PduPart.CONTENT_TRANSFER_ENCODING
- .equalsIgnoreCase(new String(tempHeader))) {
- part.setContentTransferEncoding(tempValue);
- }
-
- tempPos = pduDataStream.available();
- lastLen = length - (startPos - tempPos);
- } else {
- if (LOCAL_LOGV) {
- Log.v(LOG_TAG, "Not supported Part headers: " + header);
- }
- // Skip all headers of this part.
- if (-1 == skipWapValue(pduDataStream, lastLen)) {
- Log.e(LOG_TAG, "Corrupt Part headers");
- return false;
- }
- lastLen = 0;
- }
- }
-
- if (0 != lastLen) {
- Log.e(LOG_TAG, "Corrupt Part headers");
- return false;
- }
-
- return true;
- }
-
- /**
- * Check the position of a specified part.
- *
- * @param part the part to be checked
- * @return part position, THE_FIRST_PART when it's the
- * first one, THE_LAST_PART when it's the last one.
- */
- private static int checkPartPosition(PduPart part) {
- assert (null != part);
- if ((null == mTypeParam) &&
- (null == mStartParam)) {
- return THE_LAST_PART;
- }
-
- /* check part's content-id */
- if (null != mStartParam) {
- byte[] contentId = part.getContentId();
- if (null != contentId) {
- if (true == Arrays.equals(mStartParam, contentId)) {
- return THE_FIRST_PART;
- }
- }
- // This is not the first part, so append to end (keeping the original order)
- // Check b/19607294 for details of this change
- return THE_LAST_PART;
- }
-
- /* check part's content-type */
- if (null != mTypeParam) {
- byte[] contentType = part.getContentType();
- if (null != contentType) {
- if (true == Arrays.equals(mTypeParam, contentType)) {
- return THE_FIRST_PART;
- }
- }
- }
-
- return THE_LAST_PART;
- }
-
- /**
- * Check mandatory headers of a pdu.
- *
- * @param headers pdu headers
- * @return true if the pdu has all of the mandatory headers, false otherwise.
- */
- protected static boolean checkMandatoryHeader(PduHeaders headers) {
- if (null == headers) {
- return false;
- }
-
- /* get message type */
- int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
-
- /* check Mms-Version field */
- int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
- if (0 == mmsVersion) {
- // Every message should have Mms-Version field.
- return false;
- }
-
- /* check mandatory header fields */
- switch (messageType) {
- case PduHeaders.MESSAGE_TYPE_SEND_REQ:
- // Content-Type field.
- byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
- if (null == srContentType) {
- return false;
- }
-
- // From field.
- EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
- if (null == srFrom) {
- return false;
- }
-
- // Transaction-Id field.
- byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
- if (null == srTransactionId) {
- return false;
- }
-
- break;
- case PduHeaders.MESSAGE_TYPE_SEND_CONF:
- // Response-Status field.
- int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
- if (0 == scResponseStatus) {
- return false;
- }
-
- // Transaction-Id field.
- byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
- if (null == scTransactionId) {
- return false;
- }
-
- break;
- case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
- // Content-Location field.
- byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
- if (null == niContentLocation) {
- return false;
- }
-
- // Expiry field.
- long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
- if (-1 == niExpiry) {
- return false;
- }
-
- // Message-Class field.
- byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
- if (null == niMessageClass) {
- return false;
- }
-
- // Message-Size field.
- long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
- if (-1 == niMessageSize) {
- return false;
- }
-
- // Transaction-Id field.
- byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
- if (null == niTransactionId) {
- return false;
- }
-
- break;
- case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
- // Status field.
- int nriStatus = headers.getOctet(PduHeaders.STATUS);
- if (0 == nriStatus) {
- return false;
- }
-
- // Transaction-Id field.
- byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
- if (null == nriTransactionId) {
- return false;
- }
-
- break;
- case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
- // Content-Type field.
- byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
- if (null == rcContentType) {
- return false;
- }
-
- // Date field.
- long rcDate = headers.getLongInteger(PduHeaders.DATE);
- if (-1 == rcDate) {
- return false;
- }
-
- break;
- case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
- // Date field.
- long diDate = headers.getLongInteger(PduHeaders.DATE);
- if (-1 == diDate) {
- return false;
- }
-
- // Message-Id field.
- byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
- if (null == diMessageId) {
- return false;
- }
-
- // Status field.
- int diStatus = headers.getOctet(PduHeaders.STATUS);
- if (0 == diStatus) {
- return false;
- }
-
- // To field.
- EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
- if (null == diTo) {
- return false;
- }
-
- break;
- case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
- // Transaction-Id field.
- byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
- if (null == aiTransactionId) {
- return false;
- }
-
- break;
- case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
- // Date field.
- long roDate = headers.getLongInteger(PduHeaders.DATE);
- if (-1 == roDate) {
- return false;
- }
-
- // From field.
- EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
- if (null == roFrom) {
- return false;
- }
-
- // Message-Id field.
- byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
- if (null == roMessageId) {
- return false;
- }
-
- // Read-Status field.
- int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
- if (0 == roReadStatus) {
- return false;
- }
-
- // To field.
- EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
- if (null == roTo) {
- return false;
- }
-
- break;
- case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
- // From field.
- EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
- if (null == rrFrom) {
- return false;
- }
-
- // Message-Id field.
- byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
- if (null == rrMessageId) {
- return false;
- }
-
- // Read-Status field.
- int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
- if (0 == rrReadStatus) {
- return false;
- }
-
- // To field.
- EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
- if (null == rrTo) {
- return false;
- }
-
- break;
- default:
- // Parser doesn't support this message type in this version.
- return false;
- }
-
- return true;
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/PduPart.java b/src/com/android/messaging/mmslib/pdu/PduPart.java
deleted file mode 100644
index dcdb7a6..0000000
--- a/src/com/android/messaging/mmslib/pdu/PduPart.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.messaging.mmslib.pdu;
-
-import android.net.Uri;
-import android.util.SparseArray;
-
-/**
- * The pdu part.
- */
-public class PduPart {
- /**
- * Well-Known Parameters.
- */
- public static final int P_Q = 0x80;
- public static final int P_CHARSET = 0x81;
- public static final int P_LEVEL = 0x82;
- public static final int P_TYPE = 0x83;
- public static final int P_DEP_NAME = 0x85;
- public static final int P_DEP_FILENAME = 0x86;
- public static final int P_DIFFERENCES = 0x87;
- public static final int P_PADDING = 0x88;
- // This value of "TYPE" s used with Content-Type: multipart/related
- public static final int P_CT_MR_TYPE = 0x89;
- public static final int P_DEP_START = 0x8A;
- public static final int P_DEP_START_INFO = 0x8B;
- public static final int P_DEP_COMMENT = 0x8C;
- public static final int P_DEP_DOMAIN = 0x8D;
- public static final int P_MAX_AGE = 0x8E;
- public static final int P_DEP_PATH = 0x8F;
- public static final int P_SECURE = 0x90;
- public static final int P_SEC = 0x91;
- public static final int P_MAC = 0x92;
- public static final int P_CREATION_DATE = 0x93;
- public static final int P_MODIFICATION_DATE = 0x94;
- public static final int P_READ_DATE = 0x95;
- public static final int P_SIZE = 0x96;
- public static final int P_NAME = 0x97;
- public static final int P_FILENAME = 0x98;
- public static final int P_START = 0x99;
- public static final int P_START_INFO = 0x9A;
- public static final int P_COMMENT = 0x9B;
- public static final int P_DOMAIN = 0x9C;
- public static final int P_PATH = 0x9D;
-
- /**
- * Header field names.
- */
- public static final int P_CONTENT_TYPE = 0x91;
- public static final int P_CONTENT_LOCATION = 0x8E;
- public static final int P_CONTENT_ID = 0xC0;
- public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
- public static final int P_CONTENT_DISPOSITION = 0xC5;
- // The next header is unassigned header, use reserved header(0x48) value.
- public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
-
- /**
- * Content=Transfer-Encoding string.
- */
- public static final String CONTENT_TRANSFER_ENCODING =
- "Content-Transfer-Encoding";
-
- /**
- * Value of Content-Transfer-Encoding.
- */
- public static final String P_BINARY = "binary";
- public static final String P_7BIT = "7bit";
- public static final String P_8BIT = "8bit";
- public static final String P_BASE64 = "base64";
- public static final String P_QUOTED_PRINTABLE = "quoted-printable";
-
- /**
- * Value of disposition can be set to PduPart when the value is octet in
- * the PDU.
- * "from-data" instead of Form-data<Octet 128>.
- * "attachment" instead of Attachment<Octet 129>.
- * "inline" instead of Inline<Octet 130>.
- */
- static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
- static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
- static final byte[] DISPOSITION_INLINE = "inline".getBytes();
-
- /**
- * Content-Disposition value.
- */
- public static final int P_DISPOSITION_FROM_DATA = 0x80;
- public static final int P_DISPOSITION_ATTACHMENT = 0x81;
- public static final int P_DISPOSITION_INLINE = 0x82;
-
- /**
- * Header of part.
- */
- private SparseArray<Object> mPartHeader = null;
-
- /**
- * Data uri.
- */
- private Uri mUri = null;
-
- /**
- * Part data.
- */
- private byte[] mPartData = null;
-
- private static final String TAG = "PduPart";
-
- /**
- * Empty Constructor.
- */
- public PduPart() {
- mPartHeader = new SparseArray<Object>();
- }
-
- /**
- * Set part data. The data are stored as byte array.
- *
- * @param data the data
- */
- public void setData(final byte[] data) {
- mPartData = data;
- }
-
- /**
- * @return The part data or null if the data wasn't set or
- * the data is stored as Uri.
- * @see #getDataUri
- */
- public byte[] getData() {
- return mPartData;
- }
-
- /**
- * Set data uri. The data are stored as Uri.
- *
- * @param uri the uri
- */
- public void setDataUri(final Uri uri) {
- mUri = uri;
- }
-
- /**
- * @return The Uri of the part data or null if the data wasn't set or
- * the data is stored as byte array.
- * @see #getData
- */
- public Uri getDataUri() {
- return mUri;
- }
-
- /**
- * Set Content-id value
- *
- * @param contentId the content-id value
- * @throws NullPointerException if the value is null.
- */
- public void setContentId(final byte[] contentId) {
- if ((contentId == null) || (contentId.length == 0)) {
- throw new IllegalArgumentException(
- "Content-Id may not be null or empty.");
- }
-
- if ((contentId.length > 1)
- && ((char) contentId[0] == '<')
- && ((char) contentId[contentId.length - 1] == '>')) {
- mPartHeader.put(P_CONTENT_ID, contentId);
- return;
- }
-
- // Insert beginning '<' and trailing '>' for Content-Id.
- final byte[] buffer = new byte[contentId.length + 2];
- buffer[0] = (byte) (0xff & '<');
- buffer[buffer.length - 1] = (byte) (0xff & '>');
- System.arraycopy(contentId, 0, buffer, 1, contentId.length);
- mPartHeader.put(P_CONTENT_ID, buffer);
- }
-
- /**
- * Get Content-id value.
- *
- * @return the value
- */
- public byte[] getContentId() {
- return (byte[]) mPartHeader.get(P_CONTENT_ID);
- }
-
- /**
- * Set Char-set value.
- *
- * @param charset the value
- */
- public void setCharset(final int charset) {
- mPartHeader.put(P_CHARSET, charset);
- }
-
- /**
- * Get Char-set value
- *
- * @return the charset value. Return 0 if charset was not set.
- */
- public int getCharset() {
- final Integer charset = (Integer) mPartHeader.get(P_CHARSET);
- if (charset == null) {
- return 0;
- } else {
- return charset.intValue();
- }
- }
-
- /**
- * Set Content-Location value.
- *
- * @param contentLocation the value
- * @throws NullPointerException if the value is null.
- */
- public void setContentLocation(final byte[] contentLocation) {
- if (contentLocation == null) {
- throw new NullPointerException("null content-location");
- }
-
- mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
- }
-
- /**
- * Get Content-Location value.
- *
- * @return the value
- * return PduPart.disposition[0] instead of <Octet 128> (Form-data).
- * return PduPart.disposition[1] instead of <Octet 129> (Attachment).
- * return PduPart.disposition[2] instead of <Octet 130> (Inline).
- */
- public byte[] getContentLocation() {
- return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
- }
-
- /**
- * Set Content-Disposition value.
- * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
- * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
- * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
- *
- * @param contentDisposition the value
- * @throws NullPointerException if the value is null.
- */
- public void setContentDisposition(final byte[] contentDisposition) {
- if (contentDisposition == null) {
- throw new NullPointerException("null content-disposition");
- }
-
- mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
- }
-
- /**
- * Get Content-Disposition value.
- *
- * @return the value
- */
- public byte[] getContentDisposition() {
- return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
- }
-
- /**
- * Set Content-Type value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setContentType(final byte[] contentType) {
- if (contentType == null) {
- throw new NullPointerException("null content-type");
- }
-
- mPartHeader.put(P_CONTENT_TYPE, contentType);
- }
-
- /**
- * Get Content-Type value of part.
- *
- * @return the value
- */
- public byte[] getContentType() {
- return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
- }
-
- /**
- * Set Content-Transfer-Encoding value
- *
- * @param contentId the content-id value
- * @throws NullPointerException if the value is null.
- */
- public void setContentTransferEncoding(final byte[] contentTransferEncoding) {
- if (contentTransferEncoding == null) {
- throw new NullPointerException("null content-transfer-encoding");
- }
-
- mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
- }
-
- /**
- * Get Content-Transfer-Encoding value.
- *
- * @return the value
- */
- public byte[] getContentTransferEncoding() {
- return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
- }
-
- /**
- * Set Content-type parameter: name.
- *
- * @param name the name value
- * @throws NullPointerException if the value is null.
- */
- public void setName(final byte[] name) {
- if (null == name) {
- throw new NullPointerException("null content-id");
- }
-
- mPartHeader.put(P_NAME, name);
- }
-
- /**
- * Get content-type parameter: name.
- *
- * @return the name
- */
- public byte[] getName() {
- return (byte[]) mPartHeader.get(P_NAME);
- }
-
- /**
- * Get Content-disposition parameter: filename
- *
- * @param fileName the filename value
- * @throws NullPointerException if the value is null.
- */
- public void setFilename(final byte[] fileName) {
- if (null == fileName) {
- throw new NullPointerException("null content-id");
- }
-
- mPartHeader.put(P_FILENAME, fileName);
- }
-
- /**
- * Set Content-disposition parameter: filename
- *
- * @return the filename
- */
- public byte[] getFilename() {
- return (byte[]) mPartHeader.get(P_FILENAME);
- }
-
- public String generateLocation() {
- // Assumption: At least one of the content-location / name / filename
- // or content-id should be set. This is guaranteed by the PduParser
- // for incoming messages and by MM composer for outgoing messages.
- byte[] location = (byte[]) mPartHeader.get(P_NAME);
- if (null == location) {
- location = (byte[]) mPartHeader.get(P_FILENAME);
-
- if (null == location) {
- location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
- }
- }
-
- if (null == location) {
- final byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
- return "cid:" + new String(contentId);
- } else {
- return new String(location);
- }
- }
-}
-
diff --git a/src/com/android/messaging/mmslib/pdu/PduPersister.java b/src/com/android/messaging/mmslib/pdu/PduPersister.java
deleted file mode 100644
index ceb6a85..0000000
--- a/src/com/android/messaging/mmslib/pdu/PduPersister.java
+++ /dev/null
@@ -1,1683 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.messaging.mmslib.pdu;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Mms.Addr;
-import android.provider.Telephony.Mms.Part;
-import android.provider.Telephony.MmsSms;
-import android.provider.Telephony.MmsSms.PendingMessages;
-import android.support.v4.util.ArrayMap;
-import android.support.v4.util.SimpleArrayMap;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-import com.android.messaging.mmslib.MmsException;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.mmslib.util.DownloadDrmHelper;
-import com.android.messaging.mmslib.util.DrmConvertSession;
-import com.android.messaging.mmslib.util.PduCache;
-import com.android.messaging.mmslib.util.PduCacheEntry;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Map;
-
-/**
- * This class is the high-level manager of PDU storage.
- */
-public class PduPersister {
- private static final String TAG = "PduPersister";
- private static final boolean LOCAL_LOGV = false;
-
- /**
- * The uri of temporary drm objects.
- */
- public static final String TEMPORARY_DRM_OBJECT_URI =
- "content://mms/" + Long.MAX_VALUE + "/part";
-
- /**
- * Indicate that we transiently failed to process a MM.
- */
- public static final int PROC_STATUS_TRANSIENT_FAILURE = 1;
-
- /**
- * Indicate that we permanently failed to process a MM.
- */
- public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
-
- /**
- * Indicate that we have successfully processed a MM.
- */
- public static final int PROC_STATUS_COMPLETED = 3;
-
- public static final String BEGIN_VCARD = "BEGIN:VCARD";
-
- private static PduPersister sPersister;
-
- private static final PduCache PDU_CACHE_INSTANCE;
-
- private static final int[] ADDRESS_FIELDS = new int[]{
- PduHeaders.BCC,
- PduHeaders.CC,
- PduHeaders.FROM,
- PduHeaders.TO
- };
-
- public static final String[] PDU_PROJECTION = new String[]{
- Mms._ID,
- Mms.MESSAGE_BOX,
- Mms.THREAD_ID,
- Mms.RETRIEVE_TEXT,
- Mms.SUBJECT,
- Mms.CONTENT_LOCATION,
- Mms.CONTENT_TYPE,
- Mms.MESSAGE_CLASS,
- Mms.MESSAGE_ID,
- Mms.RESPONSE_TEXT,
- Mms.TRANSACTION_ID,
- Mms.CONTENT_CLASS,
- Mms.DELIVERY_REPORT,
- Mms.MESSAGE_TYPE,
- Mms.MMS_VERSION,
- Mms.PRIORITY,
- Mms.READ_REPORT,
- Mms.READ_STATUS,
- Mms.REPORT_ALLOWED,
- Mms.RETRIEVE_STATUS,
- Mms.STATUS,
- Mms.DATE,
- Mms.DELIVERY_TIME,
- Mms.EXPIRY,
- Mms.MESSAGE_SIZE,
- Mms.SUBJECT_CHARSET,
- Mms.RETRIEVE_TEXT_CHARSET,
- Mms.READ,
- Mms.SEEN,
- };
-
- public static final int PDU_COLUMN_ID = 0;
- public static final int PDU_COLUMN_MESSAGE_BOX = 1;
- public static final int PDU_COLUMN_THREAD_ID = 2;
- public static final int PDU_COLUMN_RETRIEVE_TEXT = 3;
- public static final int PDU_COLUMN_SUBJECT = 4;
- public static final int PDU_COLUMN_CONTENT_LOCATION = 5;
- public static final int PDU_COLUMN_CONTENT_TYPE = 6;
- public static final int PDU_COLUMN_MESSAGE_CLASS = 7;
- public static final int PDU_COLUMN_MESSAGE_ID = 8;
- public static final int PDU_COLUMN_RESPONSE_TEXT = 9;
- public static final int PDU_COLUMN_TRANSACTION_ID = 10;
- public static final int PDU_COLUMN_CONTENT_CLASS = 11;
- public static final int PDU_COLUMN_DELIVERY_REPORT = 12;
- public static final int PDU_COLUMN_MESSAGE_TYPE = 13;
- public static final int PDU_COLUMN_MMS_VERSION = 14;
- public static final int PDU_COLUMN_PRIORITY = 15;
- public static final int PDU_COLUMN_READ_REPORT = 16;
- public static final int PDU_COLUMN_READ_STATUS = 17;
- public static final int PDU_COLUMN_REPORT_ALLOWED = 18;
- public static final int PDU_COLUMN_RETRIEVE_STATUS = 19;
- public static final int PDU_COLUMN_STATUS = 20;
- public static final int PDU_COLUMN_DATE = 21;
- public static final int PDU_COLUMN_DELIVERY_TIME = 22;
- public static final int PDU_COLUMN_EXPIRY = 23;
- public static final int PDU_COLUMN_MESSAGE_SIZE = 24;
- public static final int PDU_COLUMN_SUBJECT_CHARSET = 25;
- public static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
- public static final int PDU_COLUMN_READ = 27;
- public static final int PDU_COLUMN_SEEN = 28;
-
- private static final String[] PART_PROJECTION = new String[] {
- Part._ID,
- Part.CHARSET,
- Part.CONTENT_DISPOSITION,
- Part.CONTENT_ID,
- Part.CONTENT_LOCATION,
- Part.CONTENT_TYPE,
- Part.FILENAME,
- Part.NAME,
- Part.TEXT
- };
-
- private static final int PART_COLUMN_ID = 0;
- private static final int PART_COLUMN_CHARSET = 1;
- private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
- private static final int PART_COLUMN_CONTENT_ID = 3;
- private static final int PART_COLUMN_CONTENT_LOCATION = 4;
- private static final int PART_COLUMN_CONTENT_TYPE = 5;
- private static final int PART_COLUMN_FILENAME = 6;
- private static final int PART_COLUMN_NAME = 7;
- private static final int PART_COLUMN_TEXT = 8;
-
- private static final SimpleArrayMap<Uri, Integer> MESSAGE_BOX_MAP;
-
- // These map are used for convenience in persist() and load().
- private static final SparseIntArray CHARSET_COLUMN_INDEX_MAP;
-
- private static final SparseIntArray ENCODED_STRING_COLUMN_INDEX_MAP;
-
- private static final SparseIntArray TEXT_STRING_COLUMN_INDEX_MAP;
-
- private static final SparseIntArray OCTET_COLUMN_INDEX_MAP;
-
- private static final SparseIntArray LONG_COLUMN_INDEX_MAP;
-
- private static final SparseArray<String> CHARSET_COLUMN_NAME_MAP;
-
- private static final SparseArray<String> ENCODED_STRING_COLUMN_NAME_MAP;
-
- private static final SparseArray<String> TEXT_STRING_COLUMN_NAME_MAP;
-
- private static final SparseArray<String> OCTET_COLUMN_NAME_MAP;
-
- private static final SparseArray<String> LONG_COLUMN_NAME_MAP;
-
- static {
- MESSAGE_BOX_MAP = new SimpleArrayMap<Uri, Integer>();
- MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX);
- MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT);
- MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS);
- MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
-
- CHARSET_COLUMN_INDEX_MAP = new SparseIntArray();
- CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
- CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
-
- CHARSET_COLUMN_NAME_MAP = new SparseArray<String>();
- CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
- CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
-
- // Encoded string field code -> column index/name map.
- ENCODED_STRING_COLUMN_INDEX_MAP = new SparseIntArray();
- ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
- ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
-
- ENCODED_STRING_COLUMN_NAME_MAP = new SparseArray<String>();
- ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
- ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
-
- // Text string field code -> column index/name map.
- TEXT_STRING_COLUMN_INDEX_MAP = new SparseIntArray();
- TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
- TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
- TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
- TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
- TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
- TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
-
- TEXT_STRING_COLUMN_NAME_MAP = new SparseArray<String>();
- TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
- TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
- TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
- TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
- TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
- TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
-
- // Octet field code -> column index/name map.
- OCTET_COLUMN_INDEX_MAP = new SparseIntArray();
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
- OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
-
- OCTET_COLUMN_NAME_MAP = new SparseArray<String>();
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
- OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
-
- // Long field code -> column index/name map.
- LONG_COLUMN_INDEX_MAP = new SparseIntArray();
- LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
- LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
- LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
- LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
-
- LONG_COLUMN_NAME_MAP = new SparseArray<String>();
- LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
- LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
- LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
- LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
-
- PDU_CACHE_INSTANCE = PduCache.getInstance();
- }
-
- private final Context mContext;
-
- private final ContentResolver mContentResolver;
-
- private PduPersister(final Context context) {
- mContext = context;
- mContentResolver = context.getContentResolver();
- }
-
- /** Get(or create if not exist) an instance of PduPersister */
- public static PduPersister getPduPersister(final Context context) {
- if ((sPersister == null) || !context.equals(sPersister.mContext)) {
- sPersister = new PduPersister(context);
- }
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "PduPersister getPduPersister");
- }
-
- return sPersister;
- }
-
- private void setEncodedStringValueToHeaders(
- final Cursor c, final int columnIndex,
- final PduHeaders headers, final int mapColumn) {
- final String s = c.getString(columnIndex);
- if ((s != null) && (s.length() > 0)) {
- final int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
- final int charset = c.getInt(charsetColumnIndex);
- final EncodedStringValue value = new EncodedStringValue(
- charset, getBytes(s));
- headers.setEncodedStringValue(value, mapColumn);
- }
- }
-
- private void setTextStringToHeaders(
- final Cursor c, final int columnIndex,
- final PduHeaders headers, final int mapColumn) {
- final String s = c.getString(columnIndex);
- if (s != null) {
- headers.setTextString(getBytes(s), mapColumn);
- }
- }
-
- private void setOctetToHeaders(
- final Cursor c, final int columnIndex,
- final PduHeaders headers, final int mapColumn) throws InvalidHeaderValueException {
- if (!c.isNull(columnIndex)) {
- final int b = c.getInt(columnIndex);
- headers.setOctet(b, mapColumn);
- }
- }
-
- private void setLongToHeaders(
- final Cursor c, final int columnIndex,
- final PduHeaders headers, final int mapColumn) {
- if (!c.isNull(columnIndex)) {
- final long l = c.getLong(columnIndex);
- headers.setLongInteger(l, mapColumn);
- }
- }
-
- private Integer getIntegerFromPartColumn(final Cursor c, final int columnIndex) {
- if (!c.isNull(columnIndex)) {
- return c.getInt(columnIndex);
- }
- return null;
- }
-
- private byte[] getByteArrayFromPartColumn(final Cursor c, final int columnIndex) {
- if (!c.isNull(columnIndex)) {
- return getBytes(c.getString(columnIndex));
- }
- return null;
- }
-
- private PduPart[] loadParts(final long msgId) throws MmsException {
- final Cursor c = SqliteWrapper.query(mContext, mContentResolver,
- Uri.parse("content://mms/" + msgId + "/part"),
- PART_PROJECTION, null, null, null);
-
- PduPart[] parts = null;
-
- try {
- if ((c == null) || (c.getCount() == 0)) {
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "loadParts(" + msgId + "): no part to load.");
- }
- return null;
- }
-
- final int partCount = c.getCount();
- int partIdx = 0;
- parts = new PduPart[partCount];
- while (c.moveToNext()) {
- final PduPart part = new PduPart();
- final Integer charset = getIntegerFromPartColumn(
- c, PART_COLUMN_CHARSET);
- if (charset != null) {
- part.setCharset(charset);
- }
-
- final byte[] contentDisposition = getByteArrayFromPartColumn(
- c, PART_COLUMN_CONTENT_DISPOSITION);
- if (contentDisposition != null) {
- part.setContentDisposition(contentDisposition);
- }
-
- final byte[] contentId = getByteArrayFromPartColumn(
- c, PART_COLUMN_CONTENT_ID);
- if (contentId != null) {
- part.setContentId(contentId);
- }
-
- final byte[] contentLocation = getByteArrayFromPartColumn(
- c, PART_COLUMN_CONTENT_LOCATION);
- if (contentLocation != null) {
- part.setContentLocation(contentLocation);
- }
-
- final byte[] contentType = getByteArrayFromPartColumn(
- c, PART_COLUMN_CONTENT_TYPE);
- if (contentType != null) {
- part.setContentType(contentType);
- } else {
- throw new MmsException("Content-Type must be set.");
- }
-
- final byte[] fileName = getByteArrayFromPartColumn(
- c, PART_COLUMN_FILENAME);
- if (fileName != null) {
- part.setFilename(fileName);
- }
-
- final byte[] name = getByteArrayFromPartColumn(
- c, PART_COLUMN_NAME);
- if (name != null) {
- part.setName(name);
- }
-
- // Construct a Uri for this part.
- final long partId = c.getLong(PART_COLUMN_ID);
- final Uri partURI = Uri.parse("content://mms/part/" + partId);
- part.setDataUri(partURI);
-
- // For images/audio/video, we won't keep their data in Part
- // because their renderer accept Uri as source.
- final String type = toIsoString(contentType);
- if (!ContentType.isImageType(type)
- && !ContentType.isAudioType(type)
- && !ContentType.isVideoType(type)) {
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- InputStream is = null;
-
- // Store simple string values directly in the database instead of an
- // external file. This makes the text searchable and retrieval slightly
- // faster.
- if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type)
- || ContentType.TEXT_HTML.equals(type)) {
- final String text = c.getString(PART_COLUMN_TEXT);
- final byte[] blob = new EncodedStringValue(
- charset != null ? charset : CharacterSets.DEFAULT_CHARSET,
- text != null ? text : "")
- .getTextString();
- baos.write(blob, 0, blob.length);
- } else {
-
- try {
- is = mContentResolver.openInputStream(partURI);
-
- final byte[] buffer = new byte[256];
- int len = is.read(buffer);
- while (len >= 0) {
- baos.write(buffer, 0, len);
- len = is.read(buffer);
- }
- } catch (final IOException e) {
- Log.e(TAG, "Failed to load part data", e);
- c.close();
- throw new MmsException(e);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- Log.e(TAG, "Failed to close stream", e);
- } // Ignore
- }
- }
- }
- part.setData(baos.toByteArray());
- }
- parts[partIdx++] = part;
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- return parts;
- }
-
- private void loadAddress(final long msgId, final PduHeaders headers) {
- final Cursor c = SqliteWrapper.query(mContext, mContentResolver,
- Uri.parse("content://mms/" + msgId + "/addr"),
- new String[]{Addr.ADDRESS, Addr.CHARSET, Addr.TYPE},
- null, null, null);
-
- if (c != null) {
- try {
- while (c.moveToNext()) {
- final String addr = c.getString(0);
- if (!TextUtils.isEmpty(addr)) {
- final int addrType = c.getInt(2);
- switch (addrType) {
- case PduHeaders.FROM:
- headers.setEncodedStringValue(
- new EncodedStringValue(c.getInt(1), getBytes(addr)),
- addrType);
- break;
- case PduHeaders.TO:
- case PduHeaders.CC:
- case PduHeaders.BCC:
- headers.appendEncodedStringValue(
- new EncodedStringValue(c.getInt(1), getBytes(addr)),
- addrType);
- break;
- default:
- Log.e(TAG, "Unknown address type: " + addrType);
- break;
- }
- }
- }
- } finally {
- c.close();
- }
- }
- }
-
- /**
- * Load a PDU from a given cursor
- *
- * @param c The cursor
- * @return A parsed PDU from the database row
- */
- public GenericPdu load(final Cursor c) throws MmsException {
- final PduHeaders headers = new PduHeaders();
- final long msgId = c.getLong(PDU_COLUMN_ID);
- // Fill in the headers from the PDU columns
- loadHeadersFromCursor(c, headers);
- // Load address information of the MM.
- loadAddress(msgId, headers);
- // Load parts for the PDU body
- final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
- final PduBody body = loadBody(msgId, msgType);
- return createPdu(msgType, headers, body);
- }
-
- /**
- * Load a PDU from storage by given Uri.
- *
- * @param uri The Uri of the PDU to be loaded.
- * @return A generic PDU object, it may be cast to dedicated PDU.
- * @throws MmsException Failed to load some fields of a PDU.
- */
- public GenericPdu load(final Uri uri) throws MmsException {
- GenericPdu pdu = null;
- PduCacheEntry cacheEntry = null;
- int msgBox = 0;
- final long threadId = -1;
- try {
- synchronized (PDU_CACHE_INSTANCE) {
- if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "load: " + uri + " blocked by isUpdating()");
- }
- try {
- PDU_CACHE_INSTANCE.wait();
- } catch (final InterruptedException e) {
- Log.e(TAG, "load: ", e);
- }
- }
-
- // Check if the pdu is already loaded
- cacheEntry = PDU_CACHE_INSTANCE.get(uri);
- if (cacheEntry != null) {
- return cacheEntry.getPdu();
- }
-
- // Tell the cache to indicate to other callers that this item
- // is currently being updated.
- PDU_CACHE_INSTANCE.setUpdating(uri, true);
- }
-
- final Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
- PDU_PROJECTION, null, null, null);
- final PduHeaders headers = new PduHeaders();
- final long msgId = ContentUris.parseId(uri);
-
- try {
- if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
- return null; // MMS not found
- }
-
- msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
- //threadId = c.getLong(PDU_COLUMN_THREAD_ID);
- loadHeadersFromCursor(c, headers);
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- // Check whether 'msgId' has been assigned a valid value.
- if (msgId == -1L) {
- throw new MmsException("Error! ID of the message: -1.");
- }
-
- // Load address information of the MM.
- loadAddress(msgId, headers);
-
- final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
- final PduBody body = loadBody(msgId, msgType);
- pdu = createPdu(msgType, headers, body);
- } finally {
- synchronized (PDU_CACHE_INSTANCE) {
- if (pdu != null) {
- Assert.isNull(PDU_CACHE_INSTANCE.get(uri), "Pdu exists for " + uri);
- // Update the cache entry with the real info
- cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
- PDU_CACHE_INSTANCE.put(uri, cacheEntry);
- }
- PDU_CACHE_INSTANCE.setUpdating(uri, false);
- PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead
- }
- }
- return pdu;
- }
-
- private void loadHeadersFromCursor(final Cursor c, final PduHeaders headers)
- throws InvalidHeaderValueException {
- for (int i = ENCODED_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
- setEncodedStringValueToHeaders(
- c, ENCODED_STRING_COLUMN_INDEX_MAP.valueAt(i), headers,
- ENCODED_STRING_COLUMN_INDEX_MAP.keyAt(i));
- }
- for (int i = TEXT_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
- setTextStringToHeaders(
- c, TEXT_STRING_COLUMN_INDEX_MAP.valueAt(i), headers,
- TEXT_STRING_COLUMN_INDEX_MAP.keyAt(i));
- }
- for (int i = OCTET_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
- setOctetToHeaders(
- c, OCTET_COLUMN_INDEX_MAP.valueAt(i), headers,
- OCTET_COLUMN_INDEX_MAP.keyAt(i));
- }
- for (int i = LONG_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
- setLongToHeaders(
- c, LONG_COLUMN_INDEX_MAP.valueAt(i), headers,
- LONG_COLUMN_INDEX_MAP.keyAt(i));
- }
- }
-
- private GenericPdu createPdu(final int msgType, final PduHeaders headers, final PduBody body)
- throws MmsException {
- switch (msgType) {
- case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
- return new NotificationInd(headers);
- case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
- return new DeliveryInd(headers);
- case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
- return new ReadOrigInd(headers);
- case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
- return new RetrieveConf(headers, body);
- case PduHeaders.MESSAGE_TYPE_SEND_REQ:
- return new SendReq(headers, body);
- case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
- return new AcknowledgeInd(headers);
- case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
- return new NotifyRespInd(headers);
- case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
- return new ReadRecInd(headers);
- case PduHeaders.MESSAGE_TYPE_SEND_CONF:
- case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
- case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
- case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
- case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
- case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
- case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
- case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
- case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
- case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
- case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
- case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
- throw new MmsException(
- "Unsupported PDU type: " + Integer.toHexString(msgType));
-
- default:
- throw new MmsException(
- "Unrecognized PDU type: " + Integer.toHexString(msgType));
- }
- }
-
- private PduBody loadBody(final long msgId, final int msgType) throws MmsException {
- final PduBody body = new PduBody();
-
- // For PDU which type is M_retrieve.conf or Send.req, we should
- // load multiparts and put them into the body of the PDU.
- if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
- || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
- final PduPart[] parts = loadParts(msgId);
- if (parts != null) {
- final int partsNum = parts.length;
- for (int i = 0; i < partsNum; i++) {
- body.addPart(parts[i]);
- }
- }
- }
-
- return body;
- }
-
- private void persistAddress(
- final long msgId, final int type, final EncodedStringValue[] array) {
- final ContentValues values = new ContentValues(3);
-
- for (final EncodedStringValue addr : array) {
- values.clear(); // Clear all values first.
- values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
- values.put(Addr.CHARSET, addr.getCharacterSet());
- values.put(Addr.TYPE, type);
-
- final Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
- SqliteWrapper.insert(mContext, mContentResolver, uri, values);
- }
- }
-
- private static String getPartContentType(final PduPart part) {
- return part.getContentType() == null ? null : toIsoString(part.getContentType());
- }
-
- private static void getValues(final PduPart part, final ContentValues values) {
- byte[] bytes = part.getFilename();
- if (bytes != null) {
- values.put(Part.FILENAME, new String(bytes));
- }
-
- bytes = part.getName();
- if (bytes != null) {
- values.put(Part.NAME, new String(bytes));
- }
-
- bytes = part.getContentDisposition();
- if (bytes != null) {
- values.put(Part.CONTENT_DISPOSITION, toIsoString(bytes));
- }
-
- bytes = part.getContentId();
- if (bytes != null) {
- values.put(Part.CONTENT_ID, toIsoString(bytes));
- }
-
- bytes = part.getContentLocation();
- if (bytes != null) {
- values.put(Part.CONTENT_LOCATION, toIsoString(bytes));
- }
- }
-
- public Uri persistPart(final PduPart part, final long msgId,
- final Map<Uri, InputStream> preOpenedFiles) throws MmsException {
- final Uri uri = Uri.parse("content://mms/" + msgId + "/part");
- final ContentValues values = new ContentValues(8);
-
- final int charset = part.getCharset();
- if (charset != 0) {
- values.put(Part.CHARSET, charset);
- }
-
- String contentType = getPartContentType(part);
- final byte[] data = part.getData();
-
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "PduPersister.persistPart part: " + uri + " contentType: " +
- contentType);
- }
-
- if (contentType != null) {
- // There is no "image/jpg" in Android (and it's an invalid mimetype).
- // Change it to "image/jpeg"
- if (ContentType.IMAGE_JPG.equals(contentType)) {
- contentType = ContentType.IMAGE_JPEG;
- }
-
- // On somes phones, a vcard comes in as text/plain instead of text/v-card.
- // Fix it if necessary.
- if (ContentType.TEXT_PLAIN.equals(contentType) && data != null) {
- // There might be a more efficient way to just check the beginning of the string
- // without encoding the whole thing, but we're concerned that with various
- // characters sets, just comparing the byte data to BEGIN_VCARD would not be
- // reliable.
- final String encodedDataString = new EncodedStringValue(charset, data).getString();
- if (encodedDataString != null && encodedDataString.startsWith(BEGIN_VCARD)) {
- contentType = ContentType.TEXT_VCARD;
- part.setContentType(contentType.getBytes());
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "PduPersister.persistPart part: " + uri + " contentType: " +
- contentType + " changing to vcard");
- }
- }
- }
-
- values.put(Part.CONTENT_TYPE, contentType);
- // To ensure the SMIL part is always the first part.
- if (ContentType.APP_SMIL.equals(contentType)) {
- values.put(Part.SEQ, -1);
- }
- } else {
- throw new MmsException("MIME type of the part must be set.");
- }
-
- getValues(part, values);
-
- Uri res = null;
-
- try {
- res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
- } catch (IllegalStateException e) {
- // Currently the MMS provider throws an IllegalStateException when it's out of space
- LogUtil.e(TAG, "SqliteWrapper.insert threw: ", e);
- }
-
- if (res == null) {
- throw new MmsException("Failed to persist part, return null.");
- }
-
- persistData(part, res, contentType, preOpenedFiles);
- // After successfully store the data, we should update
- // the dataUri of the part.
- part.setDataUri(res);
-
- return res;
- }
-
- /**
- * Save data of the part into storage. The source data may be given
- * by a byte[] or a Uri. If it's a byte[], directly save it
- * into storage, otherwise load source data from the dataUri and then
- * save it. If the data is an image, we may scale down it according
- * to user preference.
- *
- * @param part The PDU part which contains data to be saved.
- * @param uri The URI of the part.
- * @param contentType The MIME type of the part.
- * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
- * @throws MmsException Cannot find source data or error occurred
- * while saving the data.
- */
- private void persistData(final PduPart part, final Uri uri,
- final String contentType, final Map<Uri, InputStream> preOpenedFiles)
- throws MmsException {
- OutputStream os = null;
- InputStream is = null;
- DrmConvertSession drmConvertSession = null;
- Uri dataUri = null;
- String path = null;
-
- try {
- final byte[] data = part.getData();
- final int charset = part.getCharset();
- if (ContentType.TEXT_PLAIN.equals(contentType)
- || ContentType.APP_SMIL.equals(contentType)
- || ContentType.TEXT_HTML.equals(contentType)) {
- // Some phone could send MMS with a text part having empty data
- // Let's just skip those parts.
- // EncodedStringValue() throws NPE if data is empty
- if (data != null) {
- final ContentValues cv = new ContentValues();
- cv.put(Mms.Part.TEXT, new EncodedStringValue(charset, data).getString());
- if (mContentResolver.update(uri, cv, null, null) != 1) {
- throw new MmsException("unable to update " + uri.toString());
- }
- }
- } else {
- final boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType);
- if (isDrm) {
- if (uri != null) {
- try {
- path = convertUriToPath(mContext, uri);
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "drm uri: " + uri + " path: " + path);
- }
- final File f = new File(path);
- final long len = f.length();
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "drm path: " + path + " len: " + len);
- }
- if (len > 0) {
- // we're not going to re-persist and re-encrypt an already
- // converted drm file
- return;
- }
- } catch (final Exception e) {
- Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e);
- }
- }
- // We haven't converted the file yet, start the conversion
- drmConvertSession = DrmConvertSession.open(mContext, contentType);
- if (drmConvertSession == null) {
- throw new MmsException("Mimetype " + contentType +
- " can not be converted.");
- }
- }
- // uri can look like:
- // content://mms/part/98
- os = mContentResolver.openOutputStream(uri);
- if (os == null) {
- throw new MmsException("Failed to create output stream on " + uri);
- }
- if (data == null) {
- dataUri = part.getDataUri();
- if ((dataUri == null) || (dataUri == uri)) {
- Log.w(TAG, "Can't find data for this part.");
- return;
- }
- // dataUri can look like:
- // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715
- if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) {
- is = preOpenedFiles.get(dataUri);
- }
- if (is == null) {
- is = mContentResolver.openInputStream(dataUri);
- }
- if (is == null) {
- throw new MmsException("Failed to create input stream on " + dataUri);
- }
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "Saving data to: " + uri);
- }
-
- final byte[] buffer = new byte[8192];
- for (int len = 0; (len = is.read(buffer)) != -1; ) {
- if (!isDrm) {
- os.write(buffer, 0, len);
- } else {
- final byte[] convertedData = drmConvertSession.convert(buffer, len);
- if (convertedData != null) {
- os.write(convertedData, 0, convertedData.length);
- } else {
- throw new MmsException("Error converting drm data.");
- }
- }
- }
- } else {
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "Saving data to: " + uri);
- }
- if (!isDrm) {
- os.write(data);
- } else {
- dataUri = uri;
- final byte[] convertedData = drmConvertSession.convert(data, data.length);
- if (convertedData != null) {
- os.write(convertedData, 0, convertedData.length);
- } else {
- throw new MmsException("Error converting drm data.");
- }
- }
- }
- }
- } catch (final SQLiteException e) {
- Log.e(TAG, "Failed with SQLiteException.", e);
- throw new MmsException(e);
- } catch (final FileNotFoundException e) {
- Log.e(TAG, "Failed to open Input/Output stream.", e);
- throw new MmsException(e);
- } catch (final IOException e) {
- Log.e(TAG, "Failed to read/write data.", e);
- throw new MmsException(e);
- } finally {
- if (os != null) {
- try {
- os.close();
- } catch (final IOException e) {
- Log.e(TAG, "IOException while closing: " + os, e);
- } // Ignore
- }
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- Log.e(TAG, "IOException while closing: " + is, e);
- } // Ignore
- }
- if (drmConvertSession != null) {
- drmConvertSession.close(path);
-
- // Reset the permissions on the encrypted part file so everyone has only read
- // permission.
- final File f = new File(path);
- final ContentValues values = new ContentValues(0);
- SqliteWrapper.update(mContext, mContentResolver,
- Uri.parse("content://mms/resetFilePerm/" + f.getName()),
- values, null, null);
- }
- }
- }
-
- /**
- * This method expects uri in the following format
- * content://media/<table_name>/<row_index> (or)
- * file://sdcard/test.mp4
- * http://test.com/test.mp4
- *
- * Here <table_name> shall be "video" or "audio" or "images"
- * <row_index> the index of the content in given table
- */
- public static String convertUriToPath(final Context context, final Uri uri) {
- String path = null;
- if (null != uri) {
- final String scheme = uri.getScheme();
- if (null == scheme || scheme.equals("") ||
- scheme.equals(ContentResolver.SCHEME_FILE)) {
- path = uri.getPath();
-
- } else if (scheme.equals("http")) {
- path = uri.toString();
-
- } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
- final String[] projection = new String[] {MediaStore.MediaColumns.DATA};
- Cursor cursor = null;
- try {
- cursor = context.getContentResolver().query(uri, projection, null,
- null, null);
- if (null == cursor || 0 == cursor.getCount() || !cursor.moveToFirst()) {
- throw new IllegalArgumentException("Given Uri could not be found" +
- " in media store");
- }
- final int pathIndex =
- cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
- path = cursor.getString(pathIndex);
- } catch (final SQLiteException e) {
- throw new IllegalArgumentException("Given Uri is not formatted in a way " +
- "so that it can be found in media store.");
- } finally {
- if (null != cursor) {
- cursor.close();
- }
- }
- } else {
- throw new IllegalArgumentException("Given Uri scheme is not supported");
- }
- }
- return path;
- }
-
- private void updateAddress(
- final long msgId, final int type, final EncodedStringValue[] array) {
- // Delete old address information and then insert new ones.
- SqliteWrapper.delete(mContext, mContentResolver,
- Uri.parse("content://mms/" + msgId + "/addr"),
- Addr.TYPE + "=" + type, null);
-
- persistAddress(msgId, type, array);
- }
-
- /**
- * Update headers of a SendReq.
- *
- * @param uri The PDU which need to be updated.
- * @param pdu New headers.
- * @throws MmsException Bad URI or updating failed.
- */
- public void updateHeaders(final Uri uri, final SendReq sendReq) {
- synchronized (PDU_CACHE_INSTANCE) {
- // If the cache item is getting updated, wait until it's done updating before
- // purging it.
- if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()");
- }
- try {
- PDU_CACHE_INSTANCE.wait();
- } catch (final InterruptedException e) {
- Log.e(TAG, "updateHeaders: ", e);
- }
- }
- }
- PDU_CACHE_INSTANCE.purge(uri);
-
- final ContentValues values = new ContentValues(10);
- final byte[] contentType = sendReq.getContentType();
- if (contentType != null) {
- values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
- }
-
- final long date = sendReq.getDate();
- if (date != -1) {
- values.put(Mms.DATE, date);
- }
-
- final int deliveryReport = sendReq.getDeliveryReport();
- if (deliveryReport != 0) {
- values.put(Mms.DELIVERY_REPORT, deliveryReport);
- }
-
- final long expiry = sendReq.getExpiry();
- if (expiry != -1) {
- values.put(Mms.EXPIRY, expiry);
- }
-
- final byte[] msgClass = sendReq.getMessageClass();
- if (msgClass != null) {
- values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
- }
-
- final int priority = sendReq.getPriority();
- if (priority != 0) {
- values.put(Mms.PRIORITY, priority);
- }
-
- final int readReport = sendReq.getReadReport();
- if (readReport != 0) {
- values.put(Mms.READ_REPORT, readReport);
- }
-
- final byte[] transId = sendReq.getTransactionId();
- if (transId != null) {
- values.put(Mms.TRANSACTION_ID, toIsoString(transId));
- }
-
- final EncodedStringValue subject = sendReq.getSubject();
- if (subject != null) {
- values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
- values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
- } else {
- values.put(Mms.SUBJECT, "");
- }
-
- final long messageSize = sendReq.getMessageSize();
- if (messageSize > 0) {
- values.put(Mms.MESSAGE_SIZE, messageSize);
- }
-
- final PduHeaders headers = sendReq.getPduHeaders();
- final HashSet<String> recipients = new HashSet<String>();
- for (final int addrType : ADDRESS_FIELDS) {
- EncodedStringValue[] array = null;
- if (addrType == PduHeaders.FROM) {
- final EncodedStringValue v = headers.getEncodedStringValue(addrType);
- if (v != null) {
- array = new EncodedStringValue[1];
- array[0] = v;
- }
- } else {
- array = headers.getEncodedStringValues(addrType);
- }
-
- if (array != null) {
- final long msgId = ContentUris.parseId(uri);
- updateAddress(msgId, addrType, array);
- if (addrType == PduHeaders.TO) {
- for (final EncodedStringValue v : array) {
- if (v != null) {
- recipients.add(v.getString());
- }
- }
- }
- }
- }
- if (!recipients.isEmpty()) {
- final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients);
- values.put(Mms.THREAD_ID, threadId);
- }
-
- SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
- }
-
-
- private void updatePart(final Uri uri, final PduPart part,
- final Map<Uri, InputStream> preOpenedFiles)
- throws MmsException {
- final ContentValues values = new ContentValues(7);
-
- final int charset = part.getCharset();
- if (charset != 0) {
- values.put(Part.CHARSET, charset);
- }
-
- String contentType = null;
- if (part.getContentType() != null) {
- contentType = toIsoString(part.getContentType());
- values.put(Part.CONTENT_TYPE, contentType);
- } else {
- throw new MmsException("MIME type of the part must be set.");
- }
-
- getValues(part, values);
-
- SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
-
- // Only update the data when:
- // 1. New binary data supplied or
- // 2. The Uri of the part is different from the current one.
- if ((part.getData() != null)
- || (uri != part.getDataUri())) {
- persistData(part, uri, contentType, preOpenedFiles);
- }
- }
-
- /**
- * Update all parts of a PDU.
- *
- * @param uri The PDU which need to be updated.
- * @param body New message body of the PDU.
- * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
- * @throws MmsException Bad URI or updating failed.
- */
- public void updateParts(final Uri uri, final PduBody body,
- final Map<Uri, InputStream> preOpenedFiles)
- throws MmsException {
- try {
- PduCacheEntry cacheEntry;
- synchronized (PDU_CACHE_INSTANCE) {
- if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "updateParts: " + uri + " blocked by isUpdating()");
- }
- try {
- PDU_CACHE_INSTANCE.wait();
- } catch (final InterruptedException e) {
- Log.e(TAG, "updateParts: ", e);
- }
- cacheEntry = PDU_CACHE_INSTANCE.get(uri);
- if (cacheEntry != null) {
- ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
- }
- }
- // Tell the cache to indicate to other callers that this item
- // is currently being updated.
- PDU_CACHE_INSTANCE.setUpdating(uri, true);
- }
-
- final ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
- final ArrayMap<Uri, PduPart> toBeUpdated = new ArrayMap<Uri, PduPart>();
-
- final int partsNum = body.getPartsNum();
- final StringBuilder filter = new StringBuilder().append('(');
- for (int i = 0; i < partsNum; i++) {
- final PduPart part = body.getPart(i);
- final Uri partUri = part.getDataUri();
- if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) {
- toBeCreated.add(part);
- } else {
- toBeUpdated.put(partUri, part);
-
- // Don't use 'i > 0' to determine whether we should append
- // 'AND' since 'i = 0' may be skipped in another branch.
- if (filter.length() > 1) {
- filter.append(" AND ");
- }
-
- filter.append(Part._ID);
- filter.append("!=");
- DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
- }
- }
- filter.append(')');
-
- final long msgId = ContentUris.parseId(uri);
-
- // Remove the parts which doesn't exist anymore.
- SqliteWrapper.delete(mContext, mContentResolver,
- Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
- filter.length() > 2 ? filter.toString() : null, null);
-
- // Create new parts which didn't exist before.
- for (final PduPart part : toBeCreated) {
- persistPart(part, msgId, preOpenedFiles);
- }
-
- // Update the modified parts.
- for (final Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
- updatePart(e.getKey(), e.getValue(), preOpenedFiles);
- }
- } finally {
- synchronized (PDU_CACHE_INSTANCE) {
- PDU_CACHE_INSTANCE.setUpdating(uri, false);
- PDU_CACHE_INSTANCE.notifyAll();
- }
- }
- }
-
- /**
- * Persist a PDU object to specific location in the storage.
- *
- * @param pdu The PDU object to be stored.
- * @param uri Where to store the given PDU object.
- * @param subId Subscription id associated with this message.
- * @param subPhoneNumber TODO
- * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
- * @return A Uri which can be used to access the stored PDU.
- */
- public Uri persist(final GenericPdu pdu, final Uri uri, final int subId,
- final String subPhoneNumber, final Map<Uri, InputStream> preOpenedFiles)
- throws MmsException {
- if (uri == null) {
- throw new MmsException("Uri may not be null.");
- }
- long msgId = -1;
- try {
- msgId = ContentUris.parseId(uri);
- } catch (final NumberFormatException e) {
- // the uri ends with "inbox" or something else like that
- }
- final boolean existingUri = msgId != -1;
-
- if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {
- throw new MmsException(
- "Bad destination, must be one of "
- + "content://mms/inbox, content://mms/sent, "
- + "content://mms/drafts, content://mms/outbox, "
- + "content://mms/temp."
- );
- }
- synchronized (PDU_CACHE_INSTANCE) {
- // If the cache item is getting updated, wait until it's done updating before
- // purging it.
- if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "persist: " + uri + " blocked by isUpdating()");
- }
- try {
- PDU_CACHE_INSTANCE.wait();
- } catch (final InterruptedException e) {
- Log.e(TAG, "persist1: ", e);
- }
- }
- }
- PDU_CACHE_INSTANCE.purge(uri);
-
- final PduHeaders header = pdu.getPduHeaders();
- PduBody body = null;
- ContentValues values = new ContentValues();
-
- // Mark new messages as seen in the telephony database so that we don't have to
- // do a global "set all messages as seen" since that occasionally seems to be
- // problematic (i.e. very slow). See bug 18189471.
- values.put(Mms.SEEN, 1);
-
- //Set<Entry<Integer, String>> set;
-
- for (int i = ENCODED_STRING_COLUMN_NAME_MAP.size(); --i >= 0; ) {
- final int field = ENCODED_STRING_COLUMN_NAME_MAP.keyAt(i);
- final EncodedStringValue encodedString = header.getEncodedStringValue(field);
- if (encodedString != null) {
- final String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
- values.put(ENCODED_STRING_COLUMN_NAME_MAP.valueAt(i),
- toIsoString(encodedString.getTextString()));
- values.put(charsetColumn, encodedString.getCharacterSet());
- }
- }
-
- for (int i = TEXT_STRING_COLUMN_NAME_MAP.size(); --i >= 0; ) {
- final byte[] text = header.getTextString(TEXT_STRING_COLUMN_NAME_MAP.keyAt(i));
- if (text != null) {
- values.put(TEXT_STRING_COLUMN_NAME_MAP.valueAt(i), toIsoString(text));
- }
- }
-
- for (int i = OCTET_COLUMN_NAME_MAP.size(); --i >= 0; ) {
- final int b = header.getOctet(OCTET_COLUMN_NAME_MAP.keyAt(i));
- if (b != 0) {
- values.put(OCTET_COLUMN_NAME_MAP.valueAt(i), b);
- }
- }
-
- for (int i = LONG_COLUMN_NAME_MAP.size(); --i >= 0; ) {
- final long l = header.getLongInteger(LONG_COLUMN_NAME_MAP.keyAt(i));
- if (l != -1L) {
- values.put(LONG_COLUMN_NAME_MAP.valueAt(i), l);
- }
- }
-
- final SparseArray<EncodedStringValue[]> addressMap =
- new SparseArray<EncodedStringValue[]>(ADDRESS_FIELDS.length);
- // Save address information.
- for (final int addrType : ADDRESS_FIELDS) {
- EncodedStringValue[] array = null;
- if (addrType == PduHeaders.FROM) {
- final EncodedStringValue v = header.getEncodedStringValue(addrType);
- if (v != null) {
- array = new EncodedStringValue[1];
- array[0] = v;
- }
- } else {
- array = header.getEncodedStringValues(addrType);
- }
- addressMap.put(addrType, array);
- }
-
- final HashSet<String> recipients = new HashSet<String>();
- final int msgType = pdu.getMessageType();
- // Here we only allocate thread ID for M-Notification.ind,
- // M-Retrieve.conf and M-Send.req.
- // Some of other PDU types may be allocated a thread ID outside
- // this scope.
- if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
- || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
- || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
- switch (msgType) {
- case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
- case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
- loadRecipients(PduHeaders.FROM, recipients, addressMap);
-
- // For received messages (whether group MMS is enabled or not) we want to
- // associate this message with the thread composed of all the recipients
- // EXCLUDING our own number. This includes the person who sent the
- // message (the FROM field above) in addition to the other people the message
- // was addressed TO (or CC fields to address group messaging compatibility
- // issues with devices that place numbers in this field). Typically our own
- // number is in the TO/CC field so we have to remove it in loadRecipients.
- checkAndLoadToCcRecipients(recipients, addressMap, subPhoneNumber);
- break;
- case PduHeaders.MESSAGE_TYPE_SEND_REQ:
- loadRecipients(PduHeaders.TO, recipients, addressMap);
- break;
- }
- long threadId = -1L;
- if (!recipients.isEmpty()) {
- // Given all the recipients associated with this message, find (or create) the
- // correct thread.
- threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients);
- } else {
- LogUtil.w(TAG, "PduPersister.persist No recipients; persisting PDU to thread: "
- + threadId);
- }
- values.put(Mms.THREAD_ID, threadId);
- }
-
- // Save parts first to avoid inconsistent message is loaded
- // while saving the parts.
- final long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
-
- // Figure out if this PDU is a text-only message
- boolean textOnly = true;
-
- // Get body if the PDU is a RetrieveConf or SendReq.
- if (pdu instanceof MultimediaMessagePdu) {
- body = ((MultimediaMessagePdu) pdu).getBody();
- // Start saving parts if necessary.
- if (body != null) {
- final int partsNum = body.getPartsNum();
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "PduPersister.persist partsNum: " + partsNum);
- }
- if (partsNum > 2) {
- // For a text-only message there will be two parts: 1-the SMIL, 2-the text.
- // Down a few lines below we're checking to make sure we've only got SMIL or
- // text. We also have to check then we don't have more than two parts.
- // Otherwise, a slideshow with two text slides would be marked as textOnly.
- textOnly = false;
- }
- for (int i = 0; i < partsNum; i++) {
- final PduPart part = body.getPart(i);
- persistPart(part, dummyId, preOpenedFiles);
-
- // If we've got anything besides text/plain or SMIL part, then we've got
- // an mms message with some other type of attachment.
- final String contentType = getPartContentType(part);
- if (LOCAL_LOGV) {
- LogUtil.v(TAG, "PduPersister.persist part: " + i + " contentType: " +
- contentType);
- }
- if (contentType != null && !ContentType.APP_SMIL.equals(contentType)
- && !ContentType.TEXT_PLAIN.equals(contentType)) {
- textOnly = false;
- }
- }
- }
- }
- // Record whether this mms message is a simple plain text or not. This is a hint for the
- // UI.
- if (OsUtil.isAtLeastJB_MR1()) {
- values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);
- }
-
- if (OsUtil.isAtLeastL_MR1()) {
- values.put(Mms.SUBSCRIPTION_ID, subId);
- } else {
- Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId);
- }
-
- Uri res = null;
- if (existingUri) {
- res = uri;
- SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);
- } else {
- res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
- if (res == null) {
- throw new MmsException("persist() failed: return null.");
- }
- // Get the real ID of the PDU and update all parts which were
- // saved with the dummy ID.
- msgId = ContentUris.parseId(res);
- }
-
- values = new ContentValues(1);
- values.put(Part.MSG_ID, msgId);
- SqliteWrapper.update(mContext, mContentResolver,
- Uri.parse("content://mms/" + dummyId + "/part"),
- values, null, null);
- // We should return the longest URI of the persisted PDU, for
- // example, if input URI is "content://mms/inbox" and the _ID of
- // persisted PDU is '8', we should return "content://mms/inbox/8"
- // instead of "content://mms/8".
- // TODO: Should the MmsProvider be responsible for this???
- if (!existingUri) {
- res = Uri.parse(uri + "/" + msgId);
- }
-
- // Save address information.
- for (final int addrType : ADDRESS_FIELDS) {
- final EncodedStringValue[] array = addressMap.get(addrType);
- if (array != null) {
- persistAddress(msgId, addrType, array);
- }
- }
-
- return res;
- }
-
- /**
- * For a given address type, extract the recipients from the headers.
- *
- * @param addressType can be PduHeaders.FROM or PduHeaders.TO
- * @param recipients a HashSet that is loaded with the recipients from the FROM or TO
- * headers
- * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header
- */
- private void loadRecipients(final int addressType, final HashSet<String> recipients,
- final SparseArray<EncodedStringValue[]> addressMap) {
- final EncodedStringValue[] array = addressMap.get(addressType);
- if (array == null) {
- return;
- }
- for (final EncodedStringValue v : array) {
- if (v != null) {
- final String number = v.getString();
- if (!recipients.contains(number)) {
- // Only add numbers which aren't already included.
- recipients.add(number);
- }
- }
- }
- }
-
- /**
- * For a given address type, extract the recipients from the headers.
- *
- * @param recipients a HashSet that is loaded with the recipients from the FROM or TO
- * headers
- * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header
- * @param selfNumber self phone number
- */
- private void checkAndLoadToCcRecipients(final HashSet<String> recipients,
- final SparseArray<EncodedStringValue[]> addressMap, final String selfNumber) {
- final EncodedStringValue[] arrayTo = addressMap.get(PduHeaders.TO);
- final EncodedStringValue[] arrayCc = addressMap.get(PduHeaders.CC);
- final ArrayList<String> numbers = new ArrayList<String>();
- if (arrayTo != null) {
- for (final EncodedStringValue v : arrayTo) {
- if (v != null) {
- numbers.add(v.getString());
- }
- }
- }
- if (arrayCc != null) {
- for (final EncodedStringValue v : arrayCc) {
- if (v != null) {
- numbers.add(v.getString());
- }
- }
- }
- for (final String number : numbers) {
- // Only add numbers which aren't my own number.
- if (TextUtils.isEmpty(selfNumber) || !PhoneNumberUtils.compare(number, selfNumber)) {
- if (!recipients.contains(number)) {
- // Only add numbers which aren't already included.
- recipients.add(number);
- }
- }
- }
- }
-
- /**
- * Move a PDU object from one location to another.
- *
- * @param from Specify the PDU object to be moved.
- * @param to The destination location, should be one of the following:
- * "content://mms/inbox", "content://mms/sent",
- * "content://mms/drafts", "content://mms/outbox",
- * "content://mms/trash".
- * @return New Uri of the moved PDU.
- * @throws MmsException Error occurred while moving the message.
- */
- public Uri move(final Uri from, final Uri to) throws MmsException {
- // Check whether the 'msgId' has been assigned a valid value.
- final long msgId = ContentUris.parseId(from);
- if (msgId == -1L) {
- throw new MmsException("Error! ID of the message: -1.");
- }
-
- // Get corresponding int value of destination box.
- final Integer msgBox = MESSAGE_BOX_MAP.get(to);
- if (msgBox == null) {
- throw new MmsException(
- "Bad destination, must be one of "
- + "content://mms/inbox, content://mms/sent, "
- + "content://mms/drafts, content://mms/outbox, "
- + "content://mms/temp."
- );
- }
-
- final ContentValues values = new ContentValues(1);
- values.put(Mms.MESSAGE_BOX, msgBox);
- SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
- return ContentUris.withAppendedId(to, msgId);
- }
-
- /**
- * Wrap a byte[] into a String.
- */
- public static String toIsoString(final byte[] bytes) {
- try {
- return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
- } catch (final UnsupportedEncodingException e) {
- // Impossible to reach here!
- Log.e(TAG, "ISO_8859_1 must be supported!", e);
- return "";
- }
- }
-
- /**
- * Unpack a given String into a byte[].
- */
- public static byte[] getBytes(final String data) {
- try {
- return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
- } catch (final UnsupportedEncodingException e) {
- // Impossible to reach here!
- Log.e(TAG, "ISO_8859_1 must be supported!", e);
- return new byte[0];
- }
- }
-
- /**
- * Remove all objects in the temporary path.
- */
- public void release() {
- final Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
- SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
- }
-
- /**
- * Find all messages to be sent or downloaded before certain time.
- */
- public Cursor getPendingMessages(final long dueTime) {
- final Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
- uriBuilder.appendQueryParameter("protocol", "mms");
-
- final String selection = PendingMessages.ERROR_TYPE + " < ?"
- + " AND " + PendingMessages.DUE_TIME + " <= ?";
-
- final String[] selectionArgs = new String[] {
- String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
- String.valueOf(dueTime)
- };
-
- return SqliteWrapper.query(mContext, mContentResolver,
- uriBuilder.build(), null, selection, selectionArgs,
- PendingMessages.DUE_TIME);
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java b/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java
deleted file mode 100644
index 1ce9dc1..0000000
--- a/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import java.io.ByteArrayOutputStream;
-
-public class QuotedPrintable {
- private static byte ESCAPE_CHAR = '=';
-
- /**
- * Decodes an array quoted-printable characters into an array of original bytes.
- * Escaped characters are converted back to their original representation.
- *
- * <p>
- * This function implements a subset of
- * quoted-printable encoding specification (rule #1 and rule #2)
- * as defined in RFC 1521.
- * </p>
- *
- * @param bytes array of quoted-printable characters
- * @return array of original bytes,
- * null if quoted-printable decoding is unsuccessful.
- */
- public static final byte[] decodeQuotedPrintable(byte[] bytes) {
- if (bytes == null) {
- return null;
- }
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- for (int i = 0; i < bytes.length; i++) {
- int b = bytes[i];
- if (b == ESCAPE_CHAR) {
- try {
- if ('\r' == (char) bytes[i + 1] &&
- '\n' == (char) bytes[i + 2]) {
- i += 2;
- continue;
- }
- int u = Character.digit((char) bytes[++i], 16);
- int l = Character.digit((char) bytes[++i], 16);
- if (u == -1 || l == -1) {
- return null;
- }
- buffer.write((char) ((u << 4) + l));
- } catch (ArrayIndexOutOfBoundsException e) {
- return null;
- }
- } else {
- buffer.write(b);
- }
- }
- return buffer.toByteArray();
- }
-}
diff --git a/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java b/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java
deleted file mode 100644
index 198e4c3..0000000
--- a/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-public class ReadOrigInd extends GenericPdu {
- /**
- * Empty constructor.
- * Since the Pdu corresponding to this class is constructed
- * by the Proxy-Relay server, this class is only instantiated
- * by the Pdu Parser.
- *
- * @throws InvalidHeaderValueException if error occurs.
- */
- public ReadOrigInd() throws InvalidHeaderValueException {
- super();
- setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- ReadOrigInd(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Get Date value.
- *
- * @return the value
- */
- public long getDate() {
- return mPduHeaders.getLongInteger(PduHeaders.DATE);
- }
-
- /**
- * Set Date value.
- *
- * @param value the value
- */
- public void setDate(long value) {
- mPduHeaders.setLongInteger(value, PduHeaders.DATE);
- }
-
- /**
- * Get From value.
- * From-value = Value-length
- * (Address-present-token Encoded-string-value | Insert-address-token)
- *
- * @return the value
- */
- public EncodedStringValue getFrom() {
- return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
- }
-
- /**
- * Set From value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setFrom(EncodedStringValue value) {
- mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
- }
-
- /**
- * Get Message-ID value.
- *
- * @return the value
- */
- public byte[] getMessageId() {
- return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Set Message-ID value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setMessageId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Get X-MMS-Read-status value.
- *
- * @return the value
- */
- public int getReadStatus() {
- return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
- }
-
- /**
- * Set X-MMS-Read-status value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setReadStatus(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
- }
-
- /**
- * Get To value.
- *
- * @return the value
- */
- public EncodedStringValue[] getTo() {
- return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
- }
-
- /**
- * Set To value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setTo(EncodedStringValue[] value) {
- mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
- }
-
- /*
- * Optional, not supported header fields:
- *
- * public byte[] getApplicId() {return null;}
- * public void setApplicId(byte[] value) {}
- *
- * public byte[] getAuxApplicId() {return null;}
- * public void getAuxApplicId(byte[] value) {}
- *
- * public byte[] getReplyApplicId() {return 0x00;}
- * public void setReplyApplicId(byte[] value) {}
- */
-}
diff --git a/src/com/android/messaging/mmslib/pdu/ReadRecInd.java b/src/com/android/messaging/mmslib/pdu/ReadRecInd.java
deleted file mode 100644
index c1a9ae4..0000000
--- a/src/com/android/messaging/mmslib/pdu/ReadRecInd.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-public class ReadRecInd extends GenericPdu {
- /**
- * Constructor, used when composing a M-ReadRec.ind pdu.
- *
- * @param from the from value
- * @param messageId the message ID value
- * @param mmsVersion current viersion of mms
- * @param readStatus the read status value
- * @param to the to value
- * @throws InvalidHeaderValueException if parameters are invalid.
- * @throws NullPointerException if messageId or to is null.
- */
- public ReadRecInd(EncodedStringValue from,
- byte[] messageId,
- int mmsVersion,
- int readStatus,
- EncodedStringValue[] to) throws InvalidHeaderValueException {
- super();
- setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
- setFrom(from);
- setMessageId(messageId);
- setMmsVersion(mmsVersion);
- setTo(to);
- setReadStatus(readStatus);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- ReadRecInd(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Get Date value.
- *
- * @return the value
- */
- public long getDate() {
- return mPduHeaders.getLongInteger(PduHeaders.DATE);
- }
-
- /**
- * Set Date value.
- *
- * @param value the value
- */
- public void setDate(long value) {
- mPduHeaders.setLongInteger(value, PduHeaders.DATE);
- }
-
- /**
- * Get Message-ID value.
- *
- * @return the value
- */
- public byte[] getMessageId() {
- return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Set Message-ID value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setMessageId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Get To value.
- *
- * @return the value
- */
- public EncodedStringValue[] getTo() {
- return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
- }
-
- /**
- * Set To value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setTo(EncodedStringValue[] value) {
- mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
- }
-
- /**
- * Get X-MMS-Read-status value.
- *
- * @return the value
- */
- public int getReadStatus() {
- return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
- }
-
- /**
- * Set X-MMS-Read-status value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setReadStatus(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
- }
-
- /*
- * Optional, not supported header fields:
- *
- * public byte[] getApplicId() {return null;}
- * public void setApplicId(byte[] value) {}
- *
- * public byte[] getAuxApplicId() {return null;}
- * public void getAuxApplicId(byte[] value) {}
- *
- * public byte[] getReplyApplicId() {return 0x00;}
- * public void setReplyApplicId(byte[] value) {}
- */
-}
diff --git a/src/com/android/messaging/mmslib/pdu/RetrieveConf.java b/src/com/android/messaging/mmslib/pdu/RetrieveConf.java
deleted file mode 100644
index 9e0faed..0000000
--- a/src/com/android/messaging/mmslib/pdu/RetrieveConf.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-/**
- * M-Retrive.conf Pdu.
- */
-public class RetrieveConf extends MultimediaMessagePdu {
- /**
- * Empty constructor.
- * Since the Pdu corresponding to this class is constructed
- * by the Proxy-Relay server, this class is only instantiated
- * by the Pdu Parser.
- *
- * @throws InvalidHeaderValueException if error occurs.
- */
- public RetrieveConf() throws InvalidHeaderValueException {
- super();
- setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- RetrieveConf(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Constructor with given headers and body
- *
- * @param headers Headers for this PDU.
- * @param body Body of this PDu.
- */
- RetrieveConf(PduHeaders headers, PduBody body) {
- super(headers, body);
- }
-
- /**
- * Get CC value.
- *
- * @return the value
- */
- public EncodedStringValue[] getCc() {
- return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
- }
-
- /**
- * Add a "CC" value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void addCc(EncodedStringValue value) {
- mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
- }
-
- /**
- * Get Content-type value.
- *
- * @return the value
- */
- public byte[] getContentType() {
- return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
- }
-
- /**
- * Set Content-type value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setContentType(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
- }
-
- /**
- * Get X-Mms-Delivery-Report value.
- *
- * @return the value
- */
- public int getDeliveryReport() {
- return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
- }
-
- /**
- * Set X-Mms-Delivery-Report value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setDeliveryReport(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
- }
-
- /**
- * Get From value.
- * From-value = Value-length
- * (Address-present-token Encoded-string-value | Insert-address-token)
- *
- * @return the value
- */
- public EncodedStringValue getFrom() {
- return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
- }
-
- /**
- * Set From value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setFrom(EncodedStringValue value) {
- mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
- }
-
- /**
- * Get X-Mms-Message-Class value.
- * Message-class-value = Class-identifier | Token-text
- * Class-identifier = Personal | Advertisement | Informational | Auto
- *
- * @return the value
- */
- public byte[] getMessageClass() {
- return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
- }
-
- /**
- * Set X-Mms-Message-Class value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setMessageClass(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
- }
-
- /**
- * Get Message-ID value.
- *
- * @return the value
- */
- public byte[] getMessageId() {
- return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Set Message-ID value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setMessageId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Get X-Mms-Read-Report value.
- *
- * @return the value
- */
- public int getReadReport() {
- return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
- }
-
- /**
- * Set X-Mms-Read-Report value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setReadReport(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
- }
-
- /**
- * Get X-Mms-Retrieve-Status value.
- *
- * @return the value
- */
- public int getRetrieveStatus() {
- /*
- * If the header is not there, assuming it is OK status.
- * Some carriers may choose to not send this header.
- */
- return mPduHeaders.hasHeader(PduHeaders.RETRIEVE_STATUS) ?
- mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS) : PduHeaders.RETRIEVE_STATUS_OK;
- }
-
- /**
- * Set X-Mms-Retrieve-Status value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
- }
-
- /**
- * Get X-Mms-Retrieve-Text value.
- *
- * @return the value
- */
- public EncodedStringValue getRetrieveText() {
- return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
- }
-
- /**
- * Set X-Mms-Retrieve-Text value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setRetrieveText(EncodedStringValue value) {
- mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
- }
-
- /**
- * Get X-Mms-Transaction-Id.
- *
- * @return the value
- */
- public byte[] getTransactionId() {
- return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
- }
-
- /**
- * Set X-Mms-Transaction-Id.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setTransactionId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
- }
-
- /*
- * Optional, not supported header fields:
- *
- * public byte[] getApplicId() {return null;}
- * public void setApplicId(byte[] value) {}
- *
- * public byte[] getAuxApplicId() {return null;}
- * public void getAuxApplicId(byte[] value) {}
- *
- * public byte getContentClass() {return 0x00;}
- * public void setApplicId(byte value) {}
- *
- * public byte getDrmContent() {return 0x00;}
- * public void setDrmContent(byte value) {}
- *
- * public byte getDistributionIndicator() {return 0x00;}
- * public void setDistributionIndicator(byte value) {}
- *
- * public PreviouslySentByValue getPreviouslySentBy() {return null;}
- * public void setPreviouslySentBy(PreviouslySentByValue value) {}
- *
- * public PreviouslySentDateValue getPreviouslySentDate() {}
- * public void setPreviouslySentDate(PreviouslySentDateValue value) {}
- *
- * public MmFlagsValue getMmFlags() {return null;}
- * public void setMmFlags(MmFlagsValue value) {}
- *
- * public MmStateValue getMmState() {return null;}
- * public void getMmState(MmStateValue value) {}
- *
- * public byte[] getReplaceId() {return 0x00;}
- * public void setReplaceId(byte[] value) {}
- *
- * public byte[] getReplyApplicId() {return 0x00;}
- * public void setReplyApplicId(byte[] value) {}
- *
- * public byte getReplyCharging() {return 0x00;}
- * public void setReplyCharging(byte value) {}
- *
- * public byte getReplyChargingDeadline() {return 0x00;}
- * public void setReplyChargingDeadline(byte value) {}
- *
- * public byte[] getReplyChargingId() {return 0x00;}
- * public void setReplyChargingId(byte[] value) {}
- *
- * public long getReplyChargingSize() {return 0;}
- * public void setReplyChargingSize(long value) {}
- */
-}
diff --git a/src/com/android/messaging/mmslib/pdu/SendConf.java b/src/com/android/messaging/mmslib/pdu/SendConf.java
deleted file mode 100644
index cf1399e..0000000
--- a/src/com/android/messaging/mmslib/pdu/SendConf.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.messaging.mmslib.pdu;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-public class SendConf extends GenericPdu {
- /**
- * Empty constructor.
- * Since the Pdu corresponding to this class is constructed
- * by the Proxy-Relay server, this class is only instantiated
- * by the Pdu Parser.
- *
- * @throws InvalidHeaderValueException if error occurs.
- */
- public SendConf() throws InvalidHeaderValueException {
- super();
- setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- SendConf(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Get Message-ID value.
- *
- * @return the value
- */
- public byte[] getMessageId() {
- return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Set Message-ID value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setMessageId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
- }
-
- /**
- * Get X-Mms-Response-Status.
- *
- * @return the value
- */
- public int getResponseStatus() {
- return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
- }
-
- /**
- * Set X-Mms-Response-Status.
- *
- * @param value the values
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setResponseStatus(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
- }
-
- /**
- * Get X-Mms-Transaction-Id field value.
- *
- * @return the X-Mms-Report-Allowed value
- */
- public byte[] getTransactionId() {
- return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
- }
-
- /**
- * Set X-Mms-Transaction-Id field value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setTransactionId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
- }
-
- /*
- * Optional, not supported header fields:
- *
- * public byte[] getContentLocation() {return null;}
- * public void setContentLocation(byte[] value) {}
- *
- * public EncodedStringValue getResponseText() {return null;}
- * public void setResponseText(EncodedStringValue value) {}
- *
- * public byte getStoreStatus() {return 0x00;}
- * public void setStoreStatus(byte value) {}
- *
- * public byte[] getStoreStatusText() {return null;}
- * public void setStoreStatusText(byte[] value) {}
- */
-}
diff --git a/src/com/android/messaging/mmslib/pdu/SendReq.java b/src/com/android/messaging/mmslib/pdu/SendReq.java
deleted file mode 100644
index d173c0b..0000000
--- a/src/com/android/messaging/mmslib/pdu/SendReq.java
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.messaging.mmslib.pdu;
-
-import android.util.Log;
-
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-
-public class SendReq extends MultimediaMessagePdu {
- private static final String TAG = "SendReq";
-
- public SendReq() {
- super();
-
- try {
- setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
- setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
- // TODO: Content-type must be decided according to whether
- // SMIL part present.
- setContentType("application/vnd.wap.multipart.related".getBytes());
- setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
- setTransactionId(generateTransactionId());
- } catch (InvalidHeaderValueException e) {
- // Impossible to reach here since all headers we set above are valid.
- Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
- throw new RuntimeException(e);
- }
- }
-
- private byte[] generateTransactionId() {
- String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
- return transactionId.getBytes();
- }
-
- /**
- * Constructor, used when composing a M-Send.req pdu.
- *
- * @param contentType the content type value
- * @param from the from value
- * @param mmsVersion current viersion of mms
- * @param transactionId the transaction-id value
- * @throws InvalidHeaderValueException if parameters are invalid.
- * NullPointerException if contentType, form or
- * transactionId is null.
- */
- public SendReq(byte[] contentType,
- EncodedStringValue from,
- int mmsVersion,
- byte[] transactionId) throws InvalidHeaderValueException {
- super();
- setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
- setContentType(contentType);
- setFrom(from);
- setMmsVersion(mmsVersion);
- setTransactionId(transactionId);
- }
-
- /**
- * Constructor with given headers.
- *
- * @param headers Headers for this PDU.
- */
- SendReq(PduHeaders headers) {
- super(headers);
- }
-
- /**
- * Constructor with given headers and body
- *
- * @param headers Headers for this PDU.
- * @param body Body of this PDu.
- */
- SendReq(PduHeaders headers, PduBody body) {
- super(headers, body);
- }
-
- /**
- * Get Bcc value.
- *
- * @return the value
- */
- public EncodedStringValue[] getBcc() {
- return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
- }
-
- /**
- * Add a "BCC" value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void addBcc(EncodedStringValue value) {
- mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
- }
-
- /**
- * Set "BCC" value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setBcc(EncodedStringValue[] value) {
- mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
- }
-
- /**
- * Get CC value.
- *
- * @return the value
- */
- public EncodedStringValue[] getCc() {
- return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
- }
-
- /**
- * Add a "CC" value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void addCc(EncodedStringValue value) {
- mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
- }
-
- /**
- * Set "CC" value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setCc(EncodedStringValue[] value) {
- mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
- }
-
- /**
- * Get Content-type value.
- *
- * @return the value
- */
- public byte[] getContentType() {
- return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
- }
-
- /**
- * Set Content-type value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setContentType(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
- }
-
- /**
- * Get X-Mms-Delivery-Report value.
- *
- * @return the value
- */
- public int getDeliveryReport() {
- return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
- }
-
- /**
- * Set X-Mms-Delivery-Report value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setDeliveryReport(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
- }
-
- /**
- * Get X-Mms-Expiry value.
- *
- * Expiry-value = Value-length
- * (Absolute-token Date-value | Relative-token Delta-seconds-value)
- *
- * @return the value
- */
- public long getExpiry() {
- return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
- }
-
- /**
- * Set X-Mms-Expiry value.
- *
- * @param value the value
- */
- public void setExpiry(long value) {
- mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
- }
-
- /**
- * Get X-Mms-MessageSize value.
- *
- * Expiry-value = size of message
- *
- * @return the value
- */
- public long getMessageSize() {
- return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
- }
-
- /**
- * Set X-Mms-MessageSize value.
- *
- * @param value the value
- */
- public void setMessageSize(long value) {
- mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
- }
-
- /**
- * Get X-Mms-Message-Class value.
- * Message-class-value = Class-identifier | Token-text
- * Class-identifier = Personal | Advertisement | Informational | Auto
- *
- * @return the value
- */
- public byte[] getMessageClass() {
- return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
- }
-
- /**
- * Set X-Mms-Message-Class value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setMessageClass(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
- }
-
- /**
- * Get X-Mms-Read-Report value.
- *
- * @return the value
- */
- public int getReadReport() {
- return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
- }
-
- /**
- * Set X-Mms-Read-Report value.
- *
- * @param value the value
- * @throws InvalidHeaderValueException if the value is invalid.
- */
- public void setReadReport(int value) throws InvalidHeaderValueException {
- mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
- }
-
- /**
- * Set "To" value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setTo(EncodedStringValue[] value) {
- mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
- }
-
- /**
- * Get X-Mms-Transaction-Id field value.
- *
- * @return the X-Mms-Report-Allowed value
- */
- public byte[] getTransactionId() {
- return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
- }
-
- /**
- * Set X-Mms-Transaction-Id field value.
- *
- * @param value the value
- * @throws NullPointerException if the value is null.
- */
- public void setTransactionId(byte[] value) {
- mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
- }
-
- /*
- * Optional, not supported header fields:
- *
- * public byte getAdaptationAllowed() {return 0};
- * public void setAdaptationAllowed(btye value) {};
- *
- * public byte[] getApplicId() {return null;}
- * public void setApplicId(byte[] value) {}
- *
- * public byte[] getAuxApplicId() {return null;}
- * public void getAuxApplicId(byte[] value) {}
- *
- * public byte getContentClass() {return 0x00;}
- * public void setApplicId(byte value) {}
- *
- * public long getDeliveryTime() {return 0};
- * public void setDeliveryTime(long value) {};
- *
- * public byte getDrmContent() {return 0x00;}
- * public void setDrmContent(byte value) {}
- *
- * public MmFlagsValue getMmFlags() {return null;}
- * public void setMmFlags(MmFlagsValue value) {}
- *
- * public MmStateValue getMmState() {return null;}
- * public void getMmState(MmStateValue value) {}
- *
- * public byte[] getReplyApplicId() {return 0x00;}
- * public void setReplyApplicId(byte[] value) {}
- *
- * public byte getReplyCharging() {return 0x00;}
- * public void setReplyCharging(byte value) {}
- *
- * public byte getReplyChargingDeadline() {return 0x00;}
- * public void setReplyChargingDeadline(byte value) {}
- *
- * public byte[] getReplyChargingId() {return 0x00;}
- * public void setReplyChargingId(byte[] value) {}
- *
- * public long getReplyChargingSize() {return 0;}
- * public void setReplyChargingSize(long value) {}
- *
- * public byte[] getReplyApplicId() {return 0x00;}
- * public void setReplyApplicId(byte[] value) {}
- *
- * public byte getStore() {return 0x00;}
- * public void setStore(byte value) {}
- */
-}
diff --git a/src/com/android/messaging/mmslib/util/AbstractCache.java b/src/com/android/messaging/mmslib/util/AbstractCache.java
deleted file mode 100644
index db98bb9..0000000
--- a/src/com/android/messaging/mmslib/util/AbstractCache.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2008 Esmertec AG.
- * Copyright (C) 2008 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.messaging.mmslib.util;
-
-import android.support.v4.util.SimpleArrayMap;
-import android.util.Log;
-
-public abstract class AbstractCache<K, V> {
- private static final String TAG = "AbstractCache";
- private static final boolean LOCAL_LOGV = false;
-
- private static final int MAX_CACHED_ITEMS = 500;
-
- private final SimpleArrayMap<K, CacheEntry<V>> mCacheMap;
-
- protected AbstractCache() {
- mCacheMap = new SimpleArrayMap<K, CacheEntry<V>>();
- }
-
- public boolean put(K key, V value) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Trying to put " + key + " into cache.");
- }
-
- if (mCacheMap.size() >= MAX_CACHED_ITEMS) {
- // TODO: Should remove the oldest or least hit cached entry
- // and then cache the new one.
- if (LOCAL_LOGV) {
- Log.v(TAG, "Failed! size limitation reached.");
- }
- return false;
- }
-
- if (key != null) {
- CacheEntry<V> cacheEntry = new CacheEntry<V>();
- cacheEntry.value = value;
- mCacheMap.put(key, cacheEntry);
-
- if (LOCAL_LOGV) {
- Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total.");
- }
- return true;
- }
- return false;
- }
-
- public V get(K key) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Trying to get " + key + " from cache.");
- }
-
- if (key != null) {
- CacheEntry<V> cacheEntry = mCacheMap.get(key);
- if (cacheEntry != null) {
- cacheEntry.hit++;
- if (LOCAL_LOGV) {
- Log.v(TAG, key + " hit " + cacheEntry.hit + " times.");
- }
- return cacheEntry.value;
- }
- }
- return null;
- }
-
- public V purge(K key) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Trying to purge " + key);
- }
-
- CacheEntry<V> v = mCacheMap.remove(key);
-
- if (LOCAL_LOGV) {
- Log.v(TAG, mCacheMap.size() + " items cached.");
- }
-
- return v != null ? v.value : null;
- }
-
- public void purgeAll() {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Purging cache, " + mCacheMap.size()
- + " items dropped.");
- }
- mCacheMap.clear();
- }
-
- public int size() {
- return mCacheMap.size();
- }
-
- private static class CacheEntry<V> {
-
- int hit;
-
- V value;
- }
-}
diff --git a/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java b/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java
deleted file mode 100644
index c38b179..0000000
--- a/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.mmslib.util;
-
-import android.content.Context;
-import android.drm.DrmManagerClient;
-import android.util.Log;
-
-public class DownloadDrmHelper {
- private static final String TAG = "DownloadDrmHelper";
-
- /** The MIME type of special DRM files */
- public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
-
- /** The extensions of special DRM files */
- public static final String EXTENSION_DRM_MESSAGE = ".dm";
-
- public static final String EXTENSION_INTERNAL_FWDL = ".fl";
-
- /**
- * Checks if the Media Type is a DRM Media Type
- *
- * @param drmManagerClient A DrmManagerClient
- * @param mimetype Media Type to check
- * @return True if the Media Type is DRM else false
- */
- public static boolean isDrmMimeType(Context context, String mimetype) {
- boolean result = false;
- if (context != null) {
- try {
- DrmManagerClient drmClient = new DrmManagerClient(context);
- if (drmClient != null && mimetype != null && mimetype.length() > 0) {
- result = drmClient.canHandle("", mimetype);
- }
- } catch (IllegalArgumentException e) {
- Log.w(TAG,
- "DrmManagerClient instance could not be created, context is Illegal.");
- } catch (IllegalStateException e) {
- Log.w(TAG, "DrmManagerClient didn't initialize properly.");
- }
- }
- return result;
- }
-
- /**
- * Checks if the Media Type needs to be DRM converted
- *
- * @param mimetype Media type of the content
- * @return True if convert is needed else false
- */
- public static boolean isDrmConvertNeeded(String mimetype) {
- return MIMETYPE_DRM_MESSAGE.equals(mimetype);
- }
-
- /**
- * Modifies the file extension for a DRM Forward Lock file NOTE: This
- * function shouldn't be called if the file shouldn't be DRM converted
- */
- public static String modifyDrmFwLockFileExtension(String filename) {
- if (filename != null) {
- int extensionIndex;
- extensionIndex = filename.lastIndexOf(".");
- if (extensionIndex != -1) {
- filename = filename.substring(0, extensionIndex);
- }
- filename = filename.concat(EXTENSION_INTERNAL_FWDL);
- }
- return filename;
- }
-
- /**
- * Gets the original mime type of DRM protected content.
- *
- * @param context The context
- * @param path Path to the file
- * @param containingMime The current mime type of of the file i.e. the
- * containing mime type
- * @return The original mime type of the file if DRM protected else the
- * currentMime
- */
- public static String getOriginalMimeType(Context context, String path, String containingMime) {
- String result = containingMime;
- DrmManagerClient drmClient = new DrmManagerClient(context);
- try {
- if (drmClient.canHandle(path, null)) {
- result = drmClient.getOriginalMimeType(path);
- }
- } catch (IllegalArgumentException ex) {
- Log.w(TAG,
- "Can't get original mime type since path is null or empty string.");
- } catch (IllegalStateException ex) {
- Log.w(TAG, "DrmManagerClient didn't initialize properly.");
- }
- return result;
- }
-}
diff --git a/src/com/android/messaging/mmslib/util/DrmConvertSession.java b/src/com/android/messaging/mmslib/util/DrmConvertSession.java
deleted file mode 100644
index 604e391..0000000
--- a/src/com/android/messaging/mmslib/util/DrmConvertSession.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.mmslib.util;
-
-import android.content.Context;
-import android.drm.DrmConvertedStatus;
-import android.drm.DrmManagerClient;
-import android.util.Log;
-
-import com.android.messaging.mmslib.Downloads;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-
-
-public class DrmConvertSession {
- private DrmManagerClient mDrmClient;
- private int mConvertSessionId;
- private static final String TAG = "DrmConvertSession";
-
- private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) {
- mDrmClient = drmClient;
- mConvertSessionId = convertSessionId;
- }
-
- /**
- * Start of converting a file.
- *
- * @param context The context of the application running the convert session.
- * @param mimeType Mimetype of content that shall be converted.
- * @return A convert session or null in case an error occurs.
- */
- public static DrmConvertSession open(Context context, String mimeType) {
- DrmManagerClient drmClient = null;
- int convertSessionId = -1;
- if (context != null && mimeType != null && !mimeType.equals("")) {
- try {
- drmClient = new DrmManagerClient(context);
- try {
- convertSessionId = drmClient.openConvertSession(mimeType);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "Conversion of Mimetype: " + mimeType
- + " is not supported.", e);
- } catch (IllegalStateException e) {
- Log.w(TAG, "Could not access Open DrmFramework.", e);
- }
- } catch (IllegalArgumentException e) {
- Log.w(TAG,
- "DrmManagerClient instance could not be created, context is Illegal.");
- } catch (IllegalStateException e) {
- Log.w(TAG, "DrmManagerClient didn't initialize properly.");
- }
- }
-
- if (drmClient == null || convertSessionId < 0) {
- return null;
- } else {
- return new DrmConvertSession(drmClient, convertSessionId);
- }
- }
-
- /**
- * Convert a buffer of data to protected format.
- *
- * @param buffer Buffer filled with data to convert.
- * @param size The number of bytes that shall be converted.
- * @return A Buffer filled with converted data, if execution is ok, in all
- * other case null.
- */
- public byte[] convert(byte[] inBuffer, int size) {
- byte[] result = null;
- if (inBuffer != null) {
- DrmConvertedStatus convertedStatus = null;
- try {
- if (size != inBuffer.length) {
- byte[] buf = new byte[size];
- System.arraycopy(inBuffer, 0, buf, 0, size);
- convertedStatus = mDrmClient.convertData(mConvertSessionId, buf);
- } else {
- convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer);
- }
-
- if (convertedStatus != null &&
- convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK &&
- convertedStatus.convertedData != null) {
- result = convertedStatus.convertedData;
- }
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
- + mConvertSessionId, e);
- } catch (IllegalStateException e) {
- Log.w(TAG, "Could not convert data. Convertsession: " +
- mConvertSessionId, e);
- }
- } else {
- throw new IllegalArgumentException("Parameter inBuffer is null");
- }
- return result;
- }
-
- /**
- * Ends a conversion session of a file.
- *
- * @param fileName The filename of the converted file.
- * @return Downloads.Impl.STATUS_SUCCESS if execution is ok.
- * Downloads.Impl.STATUS_FILE_ERROR in case converted file can not
- * be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem
- * occurs when accessing drm framework.
- * Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred.
- */
- public int close(String filename) {
- DrmConvertedStatus convertedStatus = null;
- int result = Downloads.Impl.STATUS_UNKNOWN_ERROR;
- if (mDrmClient != null && mConvertSessionId >= 0) {
- try {
- convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId);
- if (convertedStatus == null ||
- convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
- convertedStatus.convertedData == null) {
- result = Downloads.Impl.STATUS_NOT_ACCEPTABLE;
- } else {
- RandomAccessFile rndAccessFile = null;
- try {
- rndAccessFile = new RandomAccessFile(filename, "rw");
- rndAccessFile.seek(convertedStatus.offset);
- rndAccessFile.write(convertedStatus.convertedData);
- result = Downloads.Impl.STATUS_SUCCESS;
- } catch (FileNotFoundException e) {
- result = Downloads.Impl.STATUS_FILE_ERROR;
- Log.w(TAG, "File: " + filename + " could not be found.", e);
- } catch (IOException e) {
- result = Downloads.Impl.STATUS_FILE_ERROR;
- Log.w(TAG, "Could not access File: " + filename + " .", e);
- } catch (IllegalArgumentException e) {
- result = Downloads.Impl.STATUS_FILE_ERROR;
- Log.w(TAG, "Could not open file in mode: rw", e);
- } catch (SecurityException e) {
- Log.w(TAG, "Access to File: " + filename +
- " was denied denied by SecurityManager.", e);
- } finally {
- if (rndAccessFile != null) {
- try {
- rndAccessFile.close();
- } catch (IOException e) {
- result = Downloads.Impl.STATUS_FILE_ERROR;
- Log.w(TAG, "Failed to close File:" + filename
- + ".", e);
- }
- }
- }
- }
- } catch (IllegalStateException e) {
- Log.w(TAG, "Could not close convertsession. Convertsession: " +
- mConvertSessionId, e);
- }
- }
- return result;
- }
-}
diff --git a/src/com/android/messaging/mmslib/util/PduCache.java b/src/com/android/messaging/mmslib/util/PduCache.java
deleted file mode 100644
index 9a400c0..0000000
--- a/src/com/android/messaging/mmslib/util/PduCache.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2008 Esmertec AG.
- * Copyright (C) 2008 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.messaging.mmslib.util;
-
-import android.content.ContentUris;
-import android.content.UriMatcher;
-import android.net.Uri;
-import android.provider.Telephony.Mms;
-import android.support.v4.util.SimpleArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.util.HashSet;
-
-public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
- private static final String TAG = "PduCache";
- private static final boolean LOCAL_LOGV = false;
-
- private static final int MMS_ALL = 0;
- private static final int MMS_ALL_ID = 1;
- private static final int MMS_INBOX = 2;
- private static final int MMS_INBOX_ID = 3;
- private static final int MMS_SENT = 4;
- private static final int MMS_SENT_ID = 5;
- private static final int MMS_DRAFTS = 6;
- private static final int MMS_DRAFTS_ID = 7;
- private static final int MMS_OUTBOX = 8;
- private static final int MMS_OUTBOX_ID = 9;
- private static final int MMS_CONVERSATION = 10;
- private static final int MMS_CONVERSATION_ID = 11;
-
- private static final UriMatcher URI_MATCHER;
- private static final SparseArray<Integer> MATCH_TO_MSGBOX_ID_MAP;
-
- private static PduCache sInstance;
-
- static {
- URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
- URI_MATCHER.addURI("mms", null, MMS_ALL);
- URI_MATCHER.addURI("mms", "#", MMS_ALL_ID);
- URI_MATCHER.addURI("mms", "inbox", MMS_INBOX);
- URI_MATCHER.addURI("mms", "inbox/#", MMS_INBOX_ID);
- URI_MATCHER.addURI("mms", "sent", MMS_SENT);
- URI_MATCHER.addURI("mms", "sent/#", MMS_SENT_ID);
- URI_MATCHER.addURI("mms", "drafts", MMS_DRAFTS);
- URI_MATCHER.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
- URI_MATCHER.addURI("mms", "outbox", MMS_OUTBOX);
- URI_MATCHER.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
- URI_MATCHER.addURI("mms-sms", "conversations", MMS_CONVERSATION);
- URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
-
- MATCH_TO_MSGBOX_ID_MAP = new SparseArray<Integer>();
- MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX, Mms.MESSAGE_BOX_INBOX);
- MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT, Mms.MESSAGE_BOX_SENT);
- MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
- MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
- }
-
- private final SparseArray<HashSet<Uri>> mMessageBoxes;
- private final SimpleArrayMap<Long, HashSet<Uri>> mThreads;
- private final HashSet<Uri> mUpdating;
-
- private PduCache() {
- mMessageBoxes = new SparseArray<HashSet<Uri>>();
- mThreads = new SimpleArrayMap<Long, HashSet<Uri>>();
- mUpdating = new HashSet<Uri>();
- }
-
- public static final synchronized PduCache getInstance() {
- if (sInstance == null) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Constructing new PduCache instance.");
- }
- sInstance = new PduCache();
- }
- return sInstance;
- }
-
- @Override
- public synchronized boolean put(Uri uri, PduCacheEntry entry) {
- int msgBoxId = entry.getMessageBox();
- HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
- if (msgBox == null) {
- msgBox = new HashSet<Uri>();
- mMessageBoxes.put(msgBoxId, msgBox);
- }
-
- long threadId = entry.getThreadId();
- HashSet<Uri> thread = mThreads.get(threadId);
- if (thread == null) {
- thread = new HashSet<Uri>();
- mThreads.put(threadId, thread);
- }
-
- Uri finalKey = normalizeKey(uri);
- boolean result = super.put(finalKey, entry);
- if (result) {
- msgBox.add(finalKey);
- thread.add(finalKey);
- }
- setUpdating(uri, false);
- return result;
- }
-
- public synchronized void setUpdating(Uri uri, boolean updating) {
- if (updating) {
- mUpdating.add(uri);
- } else {
- mUpdating.remove(uri);
- }
- }
-
- public synchronized boolean isUpdating(Uri uri) {
- return mUpdating.contains(uri);
- }
-
- @Override
- public synchronized PduCacheEntry purge(Uri uri) {
- int match = URI_MATCHER.match(uri);
- switch (match) {
- case MMS_ALL_ID:
- return purgeSingleEntry(uri);
- case MMS_INBOX_ID:
- case MMS_SENT_ID:
- case MMS_DRAFTS_ID:
- case MMS_OUTBOX_ID:
- String msgId = uri.getLastPathSegment();
- return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
- // Implicit batch of purge, return null.
- case MMS_ALL:
- case MMS_CONVERSATION:
- purgeAll();
- return null;
- case MMS_INBOX:
- case MMS_SENT:
- case MMS_DRAFTS:
- case MMS_OUTBOX:
- purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
- return null;
- case MMS_CONVERSATION_ID:
- purgeByThreadId(ContentUris.parseId(uri));
- return null;
- default:
- return null;
- }
- }
-
- private PduCacheEntry purgeSingleEntry(Uri key) {
- mUpdating.remove(key);
- PduCacheEntry entry = super.purge(key);
- if (entry != null) {
- removeFromThreads(key, entry);
- removeFromMessageBoxes(key, entry);
- return entry;
- }
- return null;
- }
-
- @Override
- public synchronized void purgeAll() {
- super.purgeAll();
-
- mMessageBoxes.clear();
- mThreads.clear();
- mUpdating.clear();
- }
-
- /**
- * @param uri The Uri to be normalized.
- * @return Uri The normalized key of cached entry.
- */
- private Uri normalizeKey(Uri uri) {
- int match = URI_MATCHER.match(uri);
- Uri normalizedKey = null;
-
- switch (match) {
- case MMS_ALL_ID:
- normalizedKey = uri;
- break;
- case MMS_INBOX_ID:
- case MMS_SENT_ID:
- case MMS_DRAFTS_ID:
- case MMS_OUTBOX_ID:
- String msgId = uri.getLastPathSegment();
- normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
- break;
- default:
- return null;
- }
-
- if (LOCAL_LOGV) {
- Log.v(TAG, uri + " -> " + normalizedKey);
- }
- return normalizedKey;
- }
-
- private void purgeByMessageBox(Integer msgBoxId) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Purge cache in message box: " + msgBoxId);
- }
-
- if (msgBoxId != null) {
- HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
- mMessageBoxes.remove(msgBoxId);
- if (msgBox != null) {
- for (Uri key : msgBox) {
- mUpdating.remove(key);
- PduCacheEntry entry = super.purge(key);
- if (entry != null) {
- removeFromThreads(key, entry);
- }
- }
- }
- }
- }
-
- private void removeFromThreads(Uri key, PduCacheEntry entry) {
- HashSet<Uri> thread = mThreads.get(entry.getThreadId());
- if (thread != null) {
- thread.remove(key);
- }
- }
-
- private void purgeByThreadId(long threadId) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Purge cache in thread: " + threadId);
- }
-
- HashSet<Uri> thread = mThreads.remove(threadId);
- if (thread != null) {
- for (Uri key : thread) {
- mUpdating.remove(key);
- PduCacheEntry entry = super.purge(key);
- if (entry != null) {
- removeFromMessageBoxes(key, entry);
- }
- }
- }
- }
-
- private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
- HashSet<Uri> msgBox = mThreads.get(Long.valueOf(entry.getMessageBox()));
- if (msgBox != null) {
- msgBox.remove(key);
- }
- }
-}
diff --git a/src/com/android/messaging/mmslib/util/PduCacheEntry.java b/src/com/android/messaging/mmslib/util/PduCacheEntry.java
deleted file mode 100644
index b287f00..0000000
--- a/src/com/android/messaging/mmslib/util/PduCacheEntry.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2008 Esmertec AG.
- * Copyright (C) 2008 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.messaging.mmslib.util;
-
-import com.android.messaging.mmslib.pdu.GenericPdu;
-
-public final class PduCacheEntry {
- private final GenericPdu mPdu;
- private final int mMessageBox;
- private final long mThreadId;
-
- public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) {
- mPdu = pdu;
- mMessageBox = msgBox;
- mThreadId = threadId;
- }
-
- public GenericPdu getPdu() {
- return mPdu;
- }
-
- public int getMessageBox() {
- return mMessageBox;
- }
-
- public long getThreadId() {
- return mThreadId;
- }
-}
diff --git a/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java b/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java
deleted file mode 100644
index 9e09a2a..0000000
--- a/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Telephony;
-
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * This receiver is used to abort MMS WAP broadcasts pre-KLP when SMS is enabled.
- */
-public class AbortMmsWapPushReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction())
- && ContentType.MMS_MESSAGE.equals(intent.getType())) {
- // If we are enabled, it's our job to stop the broadcast from continuing. This
- // receiver is not used on KLP but we do an extra check here just to make sure.
- if (!OsUtil.isAtLeastKLP() && PhoneUtils.getDefault().isSmsEnabled()) {
- abortBroadcast();
- }
- }
- }
-}
diff --git a/src/com/android/messaging/receiver/AbortSmsReceiver.java b/src/com/android/messaging/receiver/AbortSmsReceiver.java
deleted file mode 100644
index f4491d8..0000000
--- a/src/com/android/messaging/receiver/AbortSmsReceiver.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * This receiver is used to abort SMS broadcasts pre-KLP when SMS is enabled.
- */
-public final class AbortSmsReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- // If we are enabled, it's our job to stop the broadcast from continuing. This
- // receiver is not used on KLP but we do an extra check here just to make sure.
- if (!OsUtil.isAtLeastKLP() && PhoneUtils.getDefault().isSmsEnabled()) {
- if (!SmsReceiver.shouldIgnoreMessage(intent)) {
- abortBroadcast();
- }
- }
- }
-}
diff --git a/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java b/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java
deleted file mode 100644
index be0d296..0000000
--- a/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import com.android.messaging.BugleApplication;
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.action.UpdateMessageNotificationAction;
-import com.android.messaging.util.BuglePrefsKeys;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Receives notification of boot completion and package replacement
- */
-public class BootAndPackageReplacedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())
- || Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) {
- // Repost unseen notifications
- Factory.get().getApplicationPrefs().putLong(
- BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP, Long.MIN_VALUE);
- UpdateMessageNotificationAction.updateMessageNotification();
-
- BugleApplication.updateAppConfig(context);
- } else {
- LogUtil.i(LogUtil.BUGLE_TAG, "BootAndPackageReplacedReceiver got unexpected action: "
- + intent.getAction());
- }
- }
-}
-
diff --git a/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java b/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java
deleted file mode 100644
index d5153a0..0000000
--- a/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import com.android.messaging.datamodel.ParticipantRefresh;
-
-/**
- * Responds to default SMS subscription selection changes from system Settings.
- */
-public class DefaultSmsSubscriptionChangeReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- ParticipantRefresh.refreshSelfParticipants();
- }
-}
diff --git a/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java b/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java
deleted file mode 100644
index a5c247c..0000000
--- a/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Telephony;
-
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Class that handles MMS WAP push intent from telephony on KLP+ Devices.
- */
-public class MmsWapPushDeliverReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION.equals(intent.getAction())
- && ContentType.MMS_MESSAGE.equals(intent.getType())) {
- // Always convert negative subIds into -1
- int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem(
- intent, MmsWapPushReceiver.EXTRA_SUBSCRIPTION);
- byte[] data = intent.getByteArrayExtra(MmsWapPushReceiver.EXTRA_DATA);
- MmsWapPushReceiver.mmsReceived(subId, data);
- }
- }
-}
diff --git a/src/com/android/messaging/receiver/MmsWapPushReceiver.java b/src/com/android/messaging/receiver/MmsWapPushReceiver.java
deleted file mode 100644
index 29cf0db..0000000
--- a/src/com/android/messaging/receiver/MmsWapPushReceiver.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Telephony;
-
-import com.android.messaging.datamodel.action.ReceiveMmsMessageAction;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Class that handles MMS WAP push intent from telephony on pre-KLP Devices.
- */
-public class MmsWapPushReceiver extends BroadcastReceiver {
- static final String EXTRA_SUBSCRIPTION = "subscription";
- static final String EXTRA_DATA = "data";
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction())
- && ContentType.MMS_MESSAGE.equals(intent.getType())) {
- if (PhoneUtils.getDefault().isSmsEnabled()) {
- // Always convert negative subIds into -1
- final int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem(
- intent, MmsWapPushReceiver.EXTRA_SUBSCRIPTION);
- final byte[] data = intent.getByteArrayExtra(MmsWapPushReceiver.EXTRA_DATA);
- mmsReceived(subId, data);
- }
- }
- }
-
- static void mmsReceived(final int subId, final byte[] data) {
- if (!PhoneUtils.getDefault().isSmsEnabled()) {
- return;
- }
-
- final ReceiveMmsMessageAction action = new ReceiveMmsMessageAction(subId, data);
- action.start();
- }
-}
-
diff --git a/src/com/android/messaging/receiver/NotificationReceiver.java b/src/com/android/messaging/receiver/NotificationReceiver.java
deleted file mode 100644
index bbb847d..0000000
--- a/src/com/android/messaging/receiver/NotificationReceiver.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.action.MarkAsSeenAction;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.ConversationIdSet;
-import com.android.messaging.util.LogUtil;
-
-// NotificationReceiver is used to handle delete intents from notifications. When a user
-// clears all notifications or swipes a bugle notification away, the intent we pass in as
-// the delete intent will get handled here.
-public class NotificationReceiver extends BroadcastReceiver {
- // Logging
- public static final String TAG = LogUtil.BUGLE_TAG;
- public static final boolean VERBOSE = false;
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (VERBOSE) {
- LogUtil.v(TAG, "NotificationReceiver.onReceive: intent " + intent);
- }
- if (intent.getAction().equals(UIIntents.ACTION_RESET_NOTIFICATIONS)) {
- final String conversationIdSetString =
- intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID_SET);
- final int notificationTargets = intent.getIntExtra(
- UIIntents.UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE, BugleNotifications.UPDATE_ALL);
- if (conversationIdSetString == null) {
- BugleNotifications.markAllMessagesAsSeen();
- } else {
- for (final String conversationId :
- ConversationIdSet.createSet(conversationIdSetString)) {
- MarkAsSeenAction.markAsSeen(conversationId);
- BugleNotifications.resetLastMessageDing(conversationId);
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/receiver/SendStatusReceiver.java b/src/com/android/messaging/receiver/SendStatusReceiver.java
deleted file mode 100644
index fc0e8c9..0000000
--- a/src/com/android/messaging/receiver/SendStatusReceiver.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.telephony.SmsMessage;
-
-import com.android.messaging.datamodel.action.ProcessDeliveryReportAction;
-import com.android.messaging.datamodel.action.ProcessDownloadedMmsAction;
-import com.android.messaging.datamodel.action.ProcessSentMessageAction;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.sms.SmsSender;
-import com.android.messaging.util.LogUtil;
-
-/**
- * The SMS sent and delivery intent receiver.
- *
- * This class just simply forwards the intents to proper recipients for actual handling.
- */
-public class SendStatusReceiver extends BroadcastReceiver {
- public static final String MESSAGE_SENT_ACTION =
- "com.android.messaging.receiver.SendStatusReceiver.MESSAGE_SENT";
- public static final String MESSAGE_DELIVERED_ACTION =
- "com.android.messaging.receiver.SendStatusReceiver.MESSAGE_DELIVERED";
- public static final String MMS_SENT_ACTION =
- "com.android.messaging.receiver.SendStatusReceiver.MMS_SENT";
- public static final String MMS_DOWNLOADED_ACTION =
- "com.android.messaging.receiver.SendStatusReceiver.MMS_DOWNLOADED";
-
- // Defined by platform, but no constant provided. See docs for SmsManager.sendTextMessage.
- public static final String EXTRA_ERROR_CODE = "errorCode";
-
- public static final String EXTRA_PART_ID = "partId";
- public static final String EXTRA_SUB_ID = "subId";
-
- public static final int NO_ERROR_CODE = 0;
- public static final int NO_PART_ID = -1;
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- // This will be called on the main thread (so it should exit quickly)
- final String action = intent.getAction();
- final int resultCode = getResultCode();
- if (MESSAGE_SENT_ACTION.equals(action)) {
- final Uri requestId = intent.getData();
- SmsSender.setResult(
- requestId,
- resultCode,
- intent.getIntExtra(EXTRA_ERROR_CODE, NO_ERROR_CODE),
- intent.getIntExtra(EXTRA_PART_ID, NO_PART_ID),
- intent.getIntExtra(EXTRA_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
- } else if (MMS_SENT_ACTION.equals(action)) {
- final Uri messageUri = intent.getData();
- ProcessSentMessageAction.processMmsSent(resultCode, messageUri,
- intent.getExtras());
- } else if (MMS_DOWNLOADED_ACTION.equals(action)) {
- ProcessDownloadedMmsAction.processMessageDownloaded(resultCode,
- intent.getExtras());
- } else if (MESSAGE_DELIVERED_ACTION.equals(action)) {
- final SmsMessage smsMessage = MmsUtils.getSmsMessageFromDeliveryReport(intent);
- final Uri smsMessageUri = intent.getData();
- if (smsMessage == null) {
- LogUtil.e(LogUtil.BUGLE_TAG, "SendStatusReceiver: empty report message");
- return;
- }
- int status = 0;
- try {
- status = smsMessage.getStatus();
- } catch (final NullPointerException e) {
- // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
- // the methods on it although the SmsMessage itself is not null.
- LogUtil.e(LogUtil.BUGLE_TAG, "SendStatusReceiver: NPE inside SmsMessage");
- return;
- }
- ProcessDeliveryReportAction.deliveryReportReceived(smsMessageUri, status);
- }
- }
-}
diff --git a/src/com/android/messaging/receiver/SmsDeliverReceiver.java b/src/com/android/messaging/receiver/SmsDeliverReceiver.java
deleted file mode 100644
index 6a9b66c..0000000
--- a/src/com/android/messaging/receiver/SmsDeliverReceiver.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Class that receives incoming SMS messages on KLP+ Devices.
- */
-public final class SmsDeliverReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- SmsReceiver.deliverSmsIntent(context, intent);
- }
-}
diff --git a/src/com/android/messaging/receiver/SmsReceiver.java b/src/com/android/messaging/receiver/SmsReceiver.java
deleted file mode 100644
index db9b4bb..0000000
--- a/src/com/android/messaging/receiver/SmsReceiver.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.provider.Telephony;
-import android.provider.Telephony.Sms;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.Builder;
-import android.support.v4.app.NotificationCompat.Style;
-import android.support.v4.app.NotificationManagerCompat;
-
-import java.util.ArrayList;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.BugleNotifications;
-import com.android.messaging.datamodel.MessageNotificationState;
-import com.android.messaging.datamodel.NoConfirmationSmsSendService;
-import com.android.messaging.datamodel.action.ReceiveSmsMessageAction;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PendingIntentConstants;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Class that receives incoming SMS messages through android.provider.Telephony.SMS_RECEIVED
- *
- * This class serves two purposes:
- * - Process phone verification SMS messages
- * - Handle SMS messages when the user has enabled us to be the default SMS app (Pre-KLP)
- */
-public final class SmsReceiver extends BroadcastReceiver {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static ArrayList<Pattern> sIgnoreSmsPatterns;
-
- /**
- * Enable or disable the SmsReceiver as appropriate. Pre-KLP we use this receiver for
- * receiving incoming SMS messages. For KLP+ this receiver is not used when running as the
- * primary user and the SmsDeliverReceiver is used for receiving incoming SMS messages.
- * When running as a secondary user, this receiver is still used to trigger the incoming
- * notification.
- */
- public static void updateSmsReceiveHandler(final Context context) {
- boolean smsReceiverEnabled;
- boolean mmsWapPushReceiverEnabled;
- boolean respondViaMessageEnabled;
- boolean broadcastAbortEnabled;
-
- if (OsUtil.isAtLeastKLP()) {
- // When we're running as the secondary user, we don't get the new SMS_DELIVER intent,
- // only the primary user receives that. As secondary, we need to go old-school and
- // listen for the SMS_RECEIVED intent. For the secondary user, use this SmsReceiver
- // for both sms and mms notification. For the primary user on KLP (and above), we don't
- // use the SmsReceiver.
- smsReceiverEnabled = OsUtil.isSecondaryUser();
- // On KLP use the new deliver event for mms
- mmsWapPushReceiverEnabled = false;
- // On KLP we need to always enable this handler to show in the list of sms apps
- respondViaMessageEnabled = true;
- // On KLP we don't need to abort the broadcast
- broadcastAbortEnabled = false;
- } else {
- // On JB we use the sms receiver for both sms/mms delivery
- final boolean carrierSmsEnabled = PhoneUtils.getDefault().isSmsEnabled();
- smsReceiverEnabled = carrierSmsEnabled;
-
- // On JB we use the mms receiver when sms/mms is enabled
- mmsWapPushReceiverEnabled = carrierSmsEnabled;
- // On JB this is dynamic to make sure we don't show in dialer if sms is disabled
- respondViaMessageEnabled = carrierSmsEnabled;
- // On JB we need to abort broadcasts if SMS is enabled
- broadcastAbortEnabled = carrierSmsEnabled;
- }
-
- final PackageManager packageManager = context.getPackageManager();
- final boolean logv = LogUtil.isLoggable(TAG, LogUtil.VERBOSE);
- if (smsReceiverEnabled) {
- if (logv) {
- LogUtil.v(TAG, "Enabling SMS message receiving");
- }
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, SmsReceiver.class),
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
-
- } else {
- if (logv) {
- LogUtil.v(TAG, "Disabling SMS message receiving");
- }
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, SmsReceiver.class),
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
- }
- if (mmsWapPushReceiverEnabled) {
- if (logv) {
- LogUtil.v(TAG, "Enabling MMS message receiving");
- }
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, MmsWapPushReceiver.class),
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
- } else {
- if (logv) {
- LogUtil.v(TAG, "Disabling MMS message receiving");
- }
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, MmsWapPushReceiver.class),
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
- }
- if (broadcastAbortEnabled) {
- if (logv) {
- LogUtil.v(TAG, "Enabling SMS/MMS broadcast abort");
- }
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, AbortSmsReceiver.class),
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, AbortMmsWapPushReceiver.class),
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
- } else {
- if (logv) {
- LogUtil.v(TAG, "Disabling SMS/MMS broadcast abort");
- }
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, AbortSmsReceiver.class),
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, AbortMmsWapPushReceiver.class),
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
- }
- if (respondViaMessageEnabled) {
- if (logv) {
- LogUtil.v(TAG, "Enabling respond via message intent");
- }
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, NoConfirmationSmsSendService.class),
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
- } else {
- if (logv) {
- LogUtil.v(TAG, "Disabling respond via message intent");
- }
- packageManager.setComponentEnabledSetting(
- new ComponentName(context, NoConfirmationSmsSendService.class),
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
- }
- }
-
- private static final String EXTRA_ERROR_CODE = "errorCode";
- private static final String EXTRA_SUB_ID = "subscription";
-
- public static void deliverSmsIntent(final Context context, final Intent intent) {
- final android.telephony.SmsMessage[] messages = getMessagesFromIntent(intent);
-
- // Check messages for validity
- if (messages == null || messages.length < 1) {
- LogUtil.e(TAG, "processReceivedSms: null or zero or ignored message");
- return;
- }
-
- final int errorCode = intent.getIntExtra(EXTRA_ERROR_CODE, 0);
- // Always convert negative subIds into -1
- int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem(
- intent, EXTRA_SUB_ID);
- deliverSmsMessages(context, subId, errorCode, messages);
- if (MmsUtils.isDumpSmsEnabled()) {
- final String format = null;
- DebugUtils.dumpSms(messages[0].getTimestampMillis(), messages, format);
- }
- }
-
- public static void deliverSmsMessages(final Context context, final int subId,
- final int errorCode, final android.telephony.SmsMessage[] messages) {
- final ContentValues messageValues =
- MmsUtils.parseReceivedSmsMessage(context, messages, errorCode);
-
- LogUtil.v(TAG, "SmsReceiver.deliverSmsMessages");
-
- final long nowInMillis = System.currentTimeMillis();
- final long receivedTimestampMs = MmsUtils.getMessageDate(messages[0], nowInMillis);
-
- messageValues.put(Sms.Inbox.DATE, receivedTimestampMs);
- // Default to unread and unseen for us but ReceiveSmsMessageAction will override
- // seen for the telephony db.
- messageValues.put(Sms.Inbox.READ, 0);
- messageValues.put(Sms.Inbox.SEEN, 0);
- if (OsUtil.isAtLeastL_MR1()) {
- messageValues.put(Sms.SUBSCRIPTION_ID, subId);
- }
-
- if (messages[0].getMessageClass() == android.telephony.SmsMessage.MessageClass.CLASS_0 ||
- DebugUtils.debugClassZeroSmsEnabled()) {
- Factory.get().getUIIntents().launchClassZeroActivity(context, messageValues);
- } else {
- final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(messageValues);
- action.start();
- }
- }
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- LogUtil.v(TAG, "SmsReceiver.onReceive " + intent);
- // On KLP+ we only take delivery of SMS messages in SmsDeliverReceiver.
- if (PhoneUtils.getDefault().isSmsEnabled()) {
- final String action = intent.getAction();
- if (OsUtil.isSecondaryUser() &&
- (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action) ||
- // TODO: update this with the actual constant from Telephony
- "android.provider.Telephony.MMS_DOWNLOADED".equals(action))) {
- postNewMessageSecondaryUserNotification();
- } else if (!OsUtil.isAtLeastKLP()) {
- deliverSmsIntent(context, intent);
- }
- }
- }
-
- private static class SecondaryUserNotificationState extends MessageNotificationState {
- SecondaryUserNotificationState() {
- super(null);
- }
-
- @Override
- protected Style build(Builder builder) {
- return null;
- }
-
- @Override
- public boolean getNotificationVibrate() {
- return true;
- }
- }
-
- public static void postNewMessageSecondaryUserNotification() {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final PendingIntent pendingIntent = UIIntents.get()
- .getPendingIntentForSecondaryUserNewMessageNotification(context);
-
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
- builder.setContentTitle(resources.getString(R.string.secondary_user_new_message_title))
- .setTicker(resources.getString(R.string.secondary_user_new_message_ticker))
- .setSmallIcon(R.drawable.ic_sms_light)
- // Returning PRIORITY_HIGH causes L to put up a HUD notification. Without it, the ticker
- // isn't displayed.
- .setPriority(Notification.PRIORITY_HIGH)
- .setContentIntent(pendingIntent);
-
- final NotificationCompat.BigTextStyle bigTextStyle =
- new NotificationCompat.BigTextStyle(builder);
- bigTextStyle.bigText(resources.getString(R.string.secondary_user_new_message_title));
- final Notification notification = bigTextStyle.build();
-
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(Factory.get().getApplicationContext());
-
- int defaults = Notification.DEFAULT_LIGHTS;
- if (BugleNotifications.shouldVibrate(new SecondaryUserNotificationState())) {
- defaults |= Notification.DEFAULT_VIBRATE;
- }
- notification.defaults = defaults;
-
- notificationManager.notify(getNotificationTag(),
- PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID, notification);
- }
-
- /**
- * Cancel the notification
- */
- public static void cancelSecondaryUserNotification() {
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(Factory.get().getApplicationContext());
- notificationManager.cancel(getNotificationTag(),
- PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID);
- }
-
- private static String getNotificationTag() {
- return Factory.get().getApplicationContext().getPackageName() + ":secondaryuser";
- }
-
- /**
- * Compile all of the patterns we check for to ignore system SMS messages.
- */
- private static void compileIgnoreSmsPatterns() {
- // Get the pattern set from GServices
- final String smsIgnoreRegex = BugleGservices.get().getString(
- BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX,
- BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT);
- if (smsIgnoreRegex != null) {
- final String[] ignoreSmsExpressions = smsIgnoreRegex.split("\n");
- if (ignoreSmsExpressions.length != 0) {
- sIgnoreSmsPatterns = new ArrayList<Pattern>();
- for (int i = 0; i < ignoreSmsExpressions.length; i++) {
- try {
- sIgnoreSmsPatterns.add(Pattern.compile(ignoreSmsExpressions[i]));
- } catch (PatternSyntaxException e) {
- LogUtil.e(TAG, "compileIgnoreSmsPatterns: Skipping bad expression: " +
- ignoreSmsExpressions[i]);
- }
- }
- }
- }
- }
-
- /**
- * Get the SMS messages from the specified SMS intent.
- * @return the messages. If there is an error or the message should be ignored, return null.
- */
- public static android.telephony.SmsMessage[] getMessagesFromIntent(Intent intent) {
- final android.telephony.SmsMessage[] messages = Sms.Intents.getMessagesFromIntent(intent);
-
- // Check messages for validity
- if (messages == null || messages.length < 1) {
- return null;
- }
- // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
- // the methods on it although the SmsMessage itself is not null. So do this check
- // before we do anything on the parsed SmsMessages.
- try {
- final String messageBody = messages[0].getDisplayMessageBody();
- if (messageBody != null) {
- // Compile patterns if necessary
- if (sIgnoreSmsPatterns == null) {
- compileIgnoreSmsPatterns();
- }
- // Check against filters
- for (final Pattern pattern : sIgnoreSmsPatterns) {
- if (pattern.matcher(messageBody).matches()) {
- return null;
- }
- }
- }
- } catch (final NullPointerException e) {
- LogUtil.e(TAG, "shouldIgnoreMessage: NPE inside SmsMessage");
- return null;
- }
- return messages;
- }
-
-
- /**
- * Check the specified SMS intent to see if the message should be ignored
- * @return true if the message should be ignored
- */
- public static boolean shouldIgnoreMessage(Intent intent) {
- return getMessagesFromIntent(intent) == null;
- }
-}
diff --git a/src/com/android/messaging/receiver/StorageStatusReceiver.java b/src/com/android/messaging/receiver/StorageStatusReceiver.java
deleted file mode 100644
index bee899c..0000000
--- a/src/com/android/messaging/receiver/StorageStatusReceiver.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import com.android.messaging.sms.SmsStorageStatusManager;
-
-/**
- * Receiver that listens on storage status changes
- */
-public class StorageStatusReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
- SmsStorageStatusManager.handleStorageLow();
- } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
- SmsStorageStatusManager.handleStorageOk();
- }
- }
-}
diff --git a/src/com/android/messaging/sms/ApnDatabase.java b/src/com/android/messaging/sms/ApnDatabase.java
deleted file mode 100644
index a8d0d0c..0000000
--- a/src/com/android/messaging/sms/ApnDatabase.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.provider.Telephony;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-/*
- * Database helper class for looking up APNs. This database has a single table
- * which stores the APNs that are initially created from an xml file.
- */
-public class ApnDatabase extends SQLiteOpenHelper {
- private static final int DB_VERSION = 3; // added sub_id columns
-
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final boolean DEBUG = false;
-
- private static Context sContext;
- private static ApnDatabase sApnDatabase;
-
- private static final String APN_DATABASE_NAME = "apn.db";
-
- /** table for carrier APN's */
- public static final String APN_TABLE = "apn";
-
- // APN table
- private static final String APN_TABLE_SQL =
- "CREATE TABLE " + APN_TABLE +
- "(_id INTEGER PRIMARY KEY," +
- Telephony.Carriers.NAME + " TEXT," +
- Telephony.Carriers.NUMERIC + " TEXT," +
- Telephony.Carriers.MCC + " TEXT," +
- Telephony.Carriers.MNC + " TEXT," +
- Telephony.Carriers.APN + " TEXT," +
- Telephony.Carriers.USER + " TEXT," +
- Telephony.Carriers.SERVER + " TEXT," +
- Telephony.Carriers.PASSWORD + " TEXT," +
- Telephony.Carriers.PROXY + " TEXT," +
- Telephony.Carriers.PORT + " TEXT," +
- Telephony.Carriers.MMSPROXY + " TEXT," +
- Telephony.Carriers.MMSPORT + " TEXT," +
- Telephony.Carriers.MMSC + " TEXT," +
- Telephony.Carriers.AUTH_TYPE + " INTEGER," +
- Telephony.Carriers.TYPE + " TEXT," +
- Telephony.Carriers.CURRENT + " INTEGER," +
- Telephony.Carriers.PROTOCOL + " TEXT," +
- Telephony.Carriers.ROAMING_PROTOCOL + " TEXT," +
- Telephony.Carriers.CARRIER_ENABLED + " BOOLEAN," +
- Telephony.Carriers.BEARER + " INTEGER," +
- Telephony.Carriers.MVNO_TYPE + " TEXT," +
- Telephony.Carriers.MVNO_MATCH_DATA + " TEXT," +
- Telephony.Carriers.SUBSCRIPTION_ID + " INTEGER DEFAULT " +
- ParticipantData.DEFAULT_SELF_SUB_ID + ");";
-
- public static final String[] APN_PROJECTION = {
- Telephony.Carriers.TYPE, // 0
- Telephony.Carriers.MMSC, // 1
- Telephony.Carriers.MMSPROXY, // 2
- Telephony.Carriers.MMSPORT, // 3
- Telephony.Carriers._ID, // 4
- Telephony.Carriers.CURRENT, // 5
- Telephony.Carriers.NUMERIC, // 6
- Telephony.Carriers.NAME, // 7
- Telephony.Carriers.MCC, // 8
- Telephony.Carriers.MNC, // 9
- Telephony.Carriers.APN, // 10
- Telephony.Carriers.SUBSCRIPTION_ID // 11
- };
-
- public static final int COLUMN_TYPE = 0;
- public static final int COLUMN_MMSC = 1;
- public static final int COLUMN_MMSPROXY = 2;
- public static final int COLUMN_MMSPORT = 3;
- public static final int COLUMN_ID = 4;
- public static final int COLUMN_CURRENT = 5;
- public static final int COLUMN_NUMERIC = 6;
- public static final int COLUMN_NAME = 7;
- public static final int COLUMN_MCC = 8;
- public static final int COLUMN_MNC = 9;
- public static final int COLUMN_APN = 10;
- public static final int COLUMN_SUB_ID = 11;
-
- public static final String[] APN_FULL_PROJECTION = {
- Telephony.Carriers.NAME,
- Telephony.Carriers.MCC,
- Telephony.Carriers.MNC,
- Telephony.Carriers.APN,
- Telephony.Carriers.USER,
- Telephony.Carriers.SERVER,
- Telephony.Carriers.PASSWORD,
- Telephony.Carriers.PROXY,
- Telephony.Carriers.PORT,
- Telephony.Carriers.MMSC,
- Telephony.Carriers.MMSPROXY,
- Telephony.Carriers.MMSPORT,
- Telephony.Carriers.AUTH_TYPE,
- Telephony.Carriers.TYPE,
- Telephony.Carriers.PROTOCOL,
- Telephony.Carriers.ROAMING_PROTOCOL,
- Telephony.Carriers.CARRIER_ENABLED,
- Telephony.Carriers.BEARER,
- Telephony.Carriers.MVNO_TYPE,
- Telephony.Carriers.MVNO_MATCH_DATA,
- Telephony.Carriers.CURRENT,
- Telephony.Carriers.SUBSCRIPTION_ID,
- };
-
- private static final String CURRENT_SELECTION = Telephony.Carriers.CURRENT + " NOT NULL";
-
- /**
- * ApnDatabase is initialized asynchronously from the application.onCreate
- * To ensure that it works in a testing environment it needs to never access the factory context
- */
- public static void initializeAppContext(final Context context) {
- sContext = context;
- }
-
- private ApnDatabase() {
- super(sContext, APN_DATABASE_NAME, null, DB_VERSION);
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase constructor");
- }
- }
-
- public static ApnDatabase getApnDatabase() {
- if (sApnDatabase == null) {
- sApnDatabase = new ApnDatabase();
- }
- return sApnDatabase;
- }
-
- public static boolean doesDatabaseExist() {
- final File dbFile = sContext.getDatabasePath(APN_DATABASE_NAME);
- return dbFile.exists();
- }
-
- @Override
- public void onCreate(final SQLiteDatabase db) {
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase onCreate");
- }
- // Build the table using defaults (apn info bundled with the app)
- rebuildTables(db);
- }
-
- /**
- * Get a copy of user changes in the old table
- *
- * @return The list of user changed apns
- */
- public static List<ContentValues> loadUserDataFromOldTable(final SQLiteDatabase db) {
- Cursor cursor = null;
- try {
- cursor = db.query(APN_TABLE,
- APN_FULL_PROJECTION, CURRENT_SELECTION,
- null/*selectionArgs*/,
- null/*groupBy*/, null/*having*/, null/*orderBy*/);
- if (cursor != null) {
- final List<ContentValues> result = Lists.newArrayList();
- while (cursor.moveToNext()) {
- final ContentValues row = cursorToValues(cursor);
- if (row != null) {
- result.add(row);
- }
- }
- return result;
- }
- } catch (final SQLiteException e) {
- LogUtil.w(TAG, "ApnDatabase.loadUserDataFromOldTable: no old user data: " + e, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return null;
- }
-
- private static final String[] ID_PROJECTION = new String[]{Telephony.Carriers._ID};
-
- private static final String ID_SELECTION = Telephony.Carriers._ID + "=?";
-
- /**
- * Store use changes of old table into the new apn table
- *
- * @param data The user changes
- */
- public static void saveUserDataFromOldTable(
- final SQLiteDatabase db, final List<ContentValues> data) {
- if (data == null || data.size() < 1) {
- return;
- }
- for (final ContentValues row : data) {
- // Build query from the row data. It is an exact match, column by column,
- // except the CURRENT column
- final StringBuilder selectionBuilder = new StringBuilder();
- final ArrayList<String> selectionArgs = Lists.newArrayList();
- for (final String key : row.keySet()) {
- if (!Telephony.Carriers.CURRENT.equals(key)) {
- if (selectionBuilder.length() > 0) {
- selectionBuilder.append(" AND ");
- }
- final String value = row.getAsString(key);
- if (TextUtils.isEmpty(value)) {
- selectionBuilder.append(key).append(" IS NULL");
- } else {
- selectionBuilder.append(key).append("=?");
- selectionArgs.add(value);
- }
- }
- }
- Cursor cursor = null;
- try {
- cursor = db.query(APN_TABLE,
- ID_PROJECTION,
- selectionBuilder.toString(),
- selectionArgs.toArray(new String[0]),
- null/*groupBy*/, null/*having*/, null/*orderBy*/);
- if (cursor != null && cursor.moveToFirst()) {
- db.update(APN_TABLE, row, ID_SELECTION, new String[]{cursor.getString(0)});
- } else {
- // User APN does not exist, insert into the new table
- row.put(Telephony.Carriers.NUMERIC,
- PhoneUtils.canonicalizeMccMnc(
- row.getAsString(Telephony.Carriers.MCC),
- row.getAsString(Telephony.Carriers.MNC))
- );
- db.insert(APN_TABLE, null/*nullColumnHack*/, row);
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "ApnDatabase.saveUserDataFromOldTable: query error " + e, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
-
- // Convert Cursor to ContentValues
- private static ContentValues cursorToValues(final Cursor cursor) {
- final int columnCount = cursor.getColumnCount();
- if (columnCount > 0) {
- final ContentValues result = new ContentValues();
- for (int i = 0; i < columnCount; i++) {
- final String name = cursor.getColumnName(i);
- final String value = cursor.getString(i);
- result.put(name, value);
- }
- return result;
- }
- return null;
- }
-
- @Override
- public void onOpen(final SQLiteDatabase db) {
- super.onOpen(db);
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase onOpen");
- }
- }
-
- @Override
- public void close() {
- super.close();
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase close");
- }
- }
-
- private void rebuildTables(final SQLiteDatabase db) {
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase rebuildTables");
- }
- db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE + ";");
- db.execSQL(APN_TABLE_SQL);
- loadApnTable(db);
- }
-
- @Override
- public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase onUpgrade");
- }
- rebuildTables(db);
- }
-
- @Override
- public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase onDowngrade");
- }
- rebuildTables(db);
- }
-
- /**
- * Load APN table from app resources
- */
- private static void loadApnTable(final SQLiteDatabase db) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "ApnDatabase loadApnTable");
- }
- final Resources r = sContext.getResources();
- final XmlResourceParser parser = r.getXml(R.xml.apns);
- final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser);
- processor.setApnHandler(new ApnsXmlProcessor.ApnHandler() {
- @Override
- public void process(final ContentValues apnValues) {
- db.insert(APN_TABLE, null/*nullColumnHack*/, apnValues);
- }
- });
- try {
- processor.process();
- } catch (final Exception e) {
- Log.e(TAG, "Got exception while loading APN database.", e);
- } finally {
- parser.close();
- }
- }
-
- public static void forceBuildAndLoadApnTables() {
- final SQLiteDatabase db = getApnDatabase().getWritableDatabase();
- db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE);
- // Table(s) always need for JB MR1 for APN support for MMS because JB MR1 throws
- // a SecurityException when trying to access the carriers table (which holds the
- // APNs). Some JB MR2 devices also throw the security exception, so we're building
- // the table for JB MR2, too.
- db.execSQL(APN_TABLE_SQL);
-
- loadApnTable(db);
- }
-
- /**
- * Clear all tables
- */
- public static void clearTables() {
- final SQLiteDatabase db = getApnDatabase().getWritableDatabase();
- db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE);
- db.execSQL(APN_TABLE_SQL);
- }
-}
diff --git a/src/com/android/messaging/sms/ApnsXmlProcessor.java b/src/com/android/messaging/sms/ApnsXmlProcessor.java
deleted file mode 100644
index 976896c..0000000
--- a/src/com/android/messaging/sms/ApnsXmlProcessor.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentValues;
-import android.provider.Telephony;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.collect.Maps;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Map;
-
-/*
- * XML processor for the following files:
- * 1. res/xml/apns.xml
- * 2. res/xml/mms_config.xml (or related overlay files)
- */
-class ApnsXmlProcessor {
- public interface ApnHandler {
- public void process(ContentValues apnValues);
- }
-
- public interface MmsConfigHandler {
- public void process(String mccMnc, String key, String value, String type);
- }
-
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final Map<String, String> APN_ATTRIBUTE_MAP = Maps.newHashMap();
- static {
- APN_ATTRIBUTE_MAP.put("mcc", Telephony.Carriers.MCC);
- APN_ATTRIBUTE_MAP.put("mnc", Telephony.Carriers.MNC);
- APN_ATTRIBUTE_MAP.put("carrier", Telephony.Carriers.NAME);
- APN_ATTRIBUTE_MAP.put("apn", Telephony.Carriers.APN);
- APN_ATTRIBUTE_MAP.put("mmsc", Telephony.Carriers.MMSC);
- APN_ATTRIBUTE_MAP.put("mmsproxy", Telephony.Carriers.MMSPROXY);
- APN_ATTRIBUTE_MAP.put("mmsport", Telephony.Carriers.MMSPORT);
- APN_ATTRIBUTE_MAP.put("type", Telephony.Carriers.TYPE);
- APN_ATTRIBUTE_MAP.put("user", Telephony.Carriers.USER);
- APN_ATTRIBUTE_MAP.put("password", Telephony.Carriers.PASSWORD);
- APN_ATTRIBUTE_MAP.put("authtype", Telephony.Carriers.AUTH_TYPE);
- APN_ATTRIBUTE_MAP.put("mvno_match_data", Telephony.Carriers.MVNO_MATCH_DATA);
- APN_ATTRIBUTE_MAP.put("mvno_type", Telephony.Carriers.MVNO_TYPE);
- APN_ATTRIBUTE_MAP.put("protocol", Telephony.Carriers.PROTOCOL);
- APN_ATTRIBUTE_MAP.put("bearer", Telephony.Carriers.BEARER);
- APN_ATTRIBUTE_MAP.put("server", Telephony.Carriers.SERVER);
- APN_ATTRIBUTE_MAP.put("roaming_protocol", Telephony.Carriers.ROAMING_PROTOCOL);
- APN_ATTRIBUTE_MAP.put("proxy", Telephony.Carriers.PROXY);
- APN_ATTRIBUTE_MAP.put("port", Telephony.Carriers.PORT);
- APN_ATTRIBUTE_MAP.put("carrier_enabled", Telephony.Carriers.CARRIER_ENABLED);
- }
-
- private static final String TAG_APNS = "apns";
- private static final String TAG_APN = "apn";
- private static final String TAG_MMS_CONFIG = "mms_config";
-
- // Handler to process one apn
- private ApnHandler mApnHandler;
- // Handler to process one mms_config key/value pair
- private MmsConfigHandler mMmsConfigHandler;
-
- private final StringBuilder mLogStringBuilder = new StringBuilder();
-
- private final XmlPullParser mInputParser;
-
- private ApnsXmlProcessor(XmlPullParser parser) {
- mInputParser = parser;
- mApnHandler = null;
- mMmsConfigHandler = null;
- }
-
- public static ApnsXmlProcessor get(XmlPullParser parser) {
- Assert.notNull(parser);
- return new ApnsXmlProcessor(parser);
- }
-
- public ApnsXmlProcessor setApnHandler(ApnHandler handler) {
- mApnHandler = handler;
- return this;
- }
-
- public ApnsXmlProcessor setMmsConfigHandler(MmsConfigHandler handler) {
- mMmsConfigHandler = handler;
- return this;
- }
-
- /**
- * Move XML parser forward to next event type or the end of doc
- *
- * @param eventType
- * @return The final event type we meet
- * @throws XmlPullParserException
- * @throws IOException
- */
- private int advanceToNextEvent(int eventType) throws XmlPullParserException, IOException {
- for (;;) {
- int nextEvent = mInputParser.next();
- if (nextEvent == eventType
- || nextEvent == XmlPullParser.END_DOCUMENT) {
- return nextEvent;
- }
- }
- }
-
- public void process() {
- try {
- // Find the first element
- if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) {
- throw new XmlPullParserException("ApnsXmlProcessor: expecting start tag @"
- + xmlParserDebugContext());
- }
- // A single ContentValues object for holding the parsing result of
- // an apn element
- final ContentValues values = new ContentValues();
- String tagName = mInputParser.getName();
- // Top level tag can be "apns" (apns.xml)
- // or "mms_config" (mms_config.xml)
- if (TAG_APNS.equals(tagName)) {
- // For "apns", there could be "apn" or both "apn" and "mms_config"
- for (;;) {
- if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) {
- break;
- }
- tagName = mInputParser.getName();
- if (TAG_APN.equals(tagName)) {
- processApn(values);
- } else if (TAG_MMS_CONFIG.equals(tagName)) {
- processMmsConfig();
- }
- }
- } else if (TAG_MMS_CONFIG.equals(tagName)) {
- // mms_config.xml resource
- processMmsConfig();
- }
- } catch (IOException e) {
- LogUtil.e(TAG, "ApnsXmlProcessor: I/O failure " + e, e);
- } catch (XmlPullParserException e) {
- LogUtil.e(TAG, "ApnsXmlProcessor: parsing failure " + e, e);
- }
- }
-
- private Integer parseInt(String text, Integer defaultValue, String logHint) {
- Integer value = defaultValue;
- try {
- value = Integer.parseInt(text);
- } catch (Exception e) {
- LogUtil.e(TAG,
- "Invalid value " + text + "for" + logHint + " @" + xmlParserDebugContext());
- }
- return value;
- }
-
- private Boolean parseBoolean(String text, Boolean defaultValue, String logHint) {
- Boolean value = defaultValue;
- try {
- value = Boolean.parseBoolean(text);
- } catch (Exception e) {
- LogUtil.e(TAG,
- "Invalid value " + text + "for" + logHint + " @" + xmlParserDebugContext());
- }
- return value;
- }
-
- private static String xmlParserEventString(int event) {
- switch (event) {
- case XmlPullParser.START_DOCUMENT: return "START_DOCUMENT";
- case XmlPullParser.END_DOCUMENT: return "END_DOCUMENT";
- case XmlPullParser.START_TAG: return "START_TAG";
- case XmlPullParser.END_TAG: return "END_TAG";
- case XmlPullParser.TEXT: return "TEXT";
- }
- return Integer.toString(event);
- }
-
- /**
- * @return The debugging information of the parser's current position
- */
- private String xmlParserDebugContext() {
- mLogStringBuilder.setLength(0);
- if (mInputParser != null) {
- try {
- final int eventType = mInputParser.getEventType();
- mLogStringBuilder.append(xmlParserEventString(eventType));
- if (eventType == XmlPullParser.START_TAG
- || eventType == XmlPullParser.END_TAG
- || eventType == XmlPullParser.TEXT) {
- mLogStringBuilder.append('<').append(mInputParser.getName());
- for (int i = 0; i < mInputParser.getAttributeCount(); i++) {
- mLogStringBuilder.append(' ')
- .append(mInputParser.getAttributeName(i))
- .append('=')
- .append(mInputParser.getAttributeValue(i));
- }
- mLogStringBuilder.append("/>");
- }
- return mLogStringBuilder.toString();
- } catch (XmlPullParserException e) {
- LogUtil.e(TAG, "xmlParserDebugContext: " + e, e);
- }
- }
- return "Unknown";
- }
-
- /**
- * Process one apn
- *
- * @param apnValues Where we store the parsed apn
- * @throws IOException
- * @throws XmlPullParserException
- */
- private void processApn(ContentValues apnValues) throws IOException, XmlPullParserException {
- Assert.notNull(apnValues);
- apnValues.clear();
- // Collect all the attributes
- for (int i = 0; i < mInputParser.getAttributeCount(); i++) {
- final String key = APN_ATTRIBUTE_MAP.get(mInputParser.getAttributeName(i));
- if (key != null) {
- apnValues.put(key, mInputParser.getAttributeValue(i));
- }
- }
- // Set numeric to be canonicalized mcc/mnc like "310120", always 6 digits
- final String canonicalMccMnc = PhoneUtils.canonicalizeMccMnc(
- apnValues.getAsString(Telephony.Carriers.MCC),
- apnValues.getAsString(Telephony.Carriers.MNC));
- apnValues.put(Telephony.Carriers.NUMERIC, canonicalMccMnc);
- // Some of the values should not be string type, converting them to desired types
- final String authType = apnValues.getAsString(Telephony.Carriers.AUTH_TYPE);
- if (authType != null) {
- apnValues.put(Telephony.Carriers.AUTH_TYPE, parseInt(authType, -1, "apn authtype"));
- }
- final String carrierEnabled = apnValues.getAsString(Telephony.Carriers.CARRIER_ENABLED);
- if (carrierEnabled != null) {
- apnValues.put(Telephony.Carriers.CARRIER_ENABLED,
- parseBoolean(carrierEnabled, null, "apn carrierEnabled"));
- }
- final String bearer = apnValues.getAsString(Telephony.Carriers.BEARER);
- if (bearer != null) {
- apnValues.put(Telephony.Carriers.BEARER, parseInt(bearer, 0, "apn bearer"));
- }
- // We are at the end tag
- if (mInputParser.next() != XmlPullParser.END_TAG) {
- throw new XmlPullParserException("Apn: expecting end tag @"
- + xmlParserDebugContext());
- }
- // We are done parsing one APN, call the handler
- if (mApnHandler != null) {
- mApnHandler.process(apnValues);
- }
- }
-
- /**
- * Process one mms_config.
- *
- * @throws IOException
- * @throws XmlPullParserException
- */
- private void processMmsConfig()
- throws IOException, XmlPullParserException {
- // Get the mcc and mnc attributes
- final String canonicalMccMnc = PhoneUtils.canonicalizeMccMnc(
- mInputParser.getAttributeValue(null, "mcc"),
- mInputParser.getAttributeValue(null, "mnc"));
- // We are at the start tag
- for (;;) {
- int nextEvent;
- // Skipping spaces
- while ((nextEvent = mInputParser.next()) == XmlPullParser.TEXT) {
- }
- if (nextEvent == XmlPullParser.START_TAG) {
- // Parse one mms config key/value
- processMmsConfigKeyValue(canonicalMccMnc);
- } else if (nextEvent == XmlPullParser.END_TAG) {
- break;
- } else {
- throw new XmlPullParserException("MmsConfig: expecting start or end tag @"
- + xmlParserDebugContext());
- }
- }
- }
-
- /**
- * Process one mms_config key/value pair
- *
- * @param mccMnc The mcc and mnc of this mms_config
- * @throws IOException
- * @throws XmlPullParserException
- */
- private void processMmsConfigKeyValue(String mccMnc)
- throws IOException, XmlPullParserException {
- final String key = mInputParser.getAttributeValue(null, "name");
- // We are at the start tag, the name of the tag is the type
- // e.g. <int name="key">value</int>
- final String type = mInputParser.getName();
- int nextEvent = mInputParser.next();
- String value = null;
- if (nextEvent == XmlPullParser.TEXT) {
- value = mInputParser.getText();
- nextEvent = mInputParser.next();
- }
- if (nextEvent != XmlPullParser.END_TAG) {
- throw new XmlPullParserException("ApnsXmlProcessor: expecting end tag @"
- + xmlParserDebugContext());
- }
- // We are done parsing one mms_config key/value, call the handler
- if (mMmsConfigHandler != null) {
- mMmsConfigHandler.process(mccMnc, key, value, type);
- }
- }
-}
diff --git a/src/com/android/messaging/sms/BugleApnSettingsLoader.java b/src/com/android/messaging/sms/BugleApnSettingsLoader.java
deleted file mode 100644
index 43c95ac..0000000
--- a/src/com/android/messaging/sms/BugleApnSettingsLoader.java
+++ /dev/null
@@ -1,646 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.provider.Telephony;
-import android.support.v7.mms.ApnSettingsLoader;
-import android.support.v7.mms.MmsManager;
-import android.text.TextUtils;
-import android.util.SparseArray;
-
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * APN loader for default SMS SIM
- *
- * This loader tries to load APNs from 3 sources in order:
- * 1. Gservices setting
- * 2. System APN table
- * 3. Local APN table
- */
-public class BugleApnSettingsLoader implements ApnSettingsLoader {
- /**
- * The base implementation of an APN
- */
- private static class BaseApn implements Apn {
- /**
- * Create a base APN from parameters
- *
- * @param typesIn the APN type field
- * @param mmscIn the APN mmsc field
- * @param proxyIn the APN mmsproxy field
- * @param portIn the APN mmsport field
- * @return an instance of base APN, or null if any of the parameter is invalid
- */
- public static BaseApn from(final String typesIn, final String mmscIn, final String proxyIn,
- final String portIn) {
- if (!isValidApnType(trimWithNullCheck(typesIn), APN_TYPE_MMS)) {
- return null;
- }
- String mmsc = trimWithNullCheck(mmscIn);
- if (TextUtils.isEmpty(mmsc)) {
- return null;
- }
- mmsc = trimV4AddrZeros(mmsc);
- try {
- new URI(mmsc);
- } catch (final URISyntaxException e) {
- return null;
- }
- String mmsProxy = trimWithNullCheck(proxyIn);
- int mmsProxyPort = 80;
- if (!TextUtils.isEmpty(mmsProxy)) {
- mmsProxy = trimV4AddrZeros(mmsProxy);
- final String portString = trimWithNullCheck(portIn);
- if (portString != null) {
- try {
- mmsProxyPort = Integer.parseInt(portString);
- } catch (final NumberFormatException e) {
- // Ignore, just use 80 to try
- }
- }
- }
- return new BaseApn(mmsc, mmsProxy, mmsProxyPort);
- }
-
- private final String mMmsc;
- private final String mMmsProxy;
- private final int mMmsProxyPort;
-
- public BaseApn(final String mmsc, final String proxy, final int port) {
- mMmsc = mmsc;
- mMmsProxy = proxy;
- mMmsProxyPort = port;
- }
-
- @Override
- public String getMmsc() {
- return mMmsc;
- }
-
- @Override
- public String getMmsProxy() {
- return mMmsProxy;
- }
-
- @Override
- public int getMmsProxyPort() {
- return mMmsProxyPort;
- }
-
- @Override
- public void setSuccess() {
- // Do nothing
- }
-
- public boolean equals(final BaseApn other) {
- return TextUtils.equals(mMmsc, other.getMmsc()) &&
- TextUtils.equals(mMmsProxy, other.getMmsProxy()) &&
- mMmsProxyPort == other.getMmsProxyPort();
- }
- }
-
- /**
- * The APN represented by the local APN table row
- */
- private static class DatabaseApn implements Apn {
- private static final ContentValues CURRENT_NULL_VALUE;
- private static final ContentValues CURRENT_SET_VALUE;
- static {
- CURRENT_NULL_VALUE = new ContentValues(1);
- CURRENT_NULL_VALUE.putNull(Telephony.Carriers.CURRENT);
- CURRENT_SET_VALUE = new ContentValues(1);
- CURRENT_SET_VALUE.put(Telephony.Carriers.CURRENT, "1"); // 1 for auto selected APN
- }
- private static final String CLEAR_UPDATE_SELECTION = Telephony.Carriers.CURRENT + " =?";
- private static final String[] CLEAR_UPDATE_SELECTION_ARGS = new String[] { "1" };
- private static final String SET_UPDATE_SELECTION = Telephony.Carriers._ID + " =?";
-
- /**
- * Create an APN loaded from local database
- *
- * @param apns the in-memory APN list
- * @param typesIn the APN type field
- * @param mmscIn the APN mmsc field
- * @param proxyIn the APN mmsproxy field
- * @param portIn the APN mmsport field
- * @param rowId the APN's row ID in database
- * @param current the value of CURRENT column in database
- * @return an in-memory APN instance for database APN row, null if parameter invalid
- */
- public static DatabaseApn from(final List<Apn> apns, final String typesIn,
- final String mmscIn, final String proxyIn, final String portIn,
- final long rowId, final int current) {
- if (apns == null) {
- return null;
- }
- final BaseApn base = BaseApn.from(typesIn, mmscIn, proxyIn, portIn);
- if (base == null) {
- return null;
- }
- for (final ApnSettingsLoader.Apn apn : apns) {
- if (apn instanceof DatabaseApn && ((DatabaseApn) apn).equals(base)) {
- return null;
- }
- }
- return new DatabaseApn(apns, base, rowId, current);
- }
-
- private final List<Apn> mApns;
- private final BaseApn mBase;
- private final long mRowId;
- private int mCurrent;
-
- public DatabaseApn(final List<Apn> apns, final BaseApn base, final long rowId,
- final int current) {
- mApns = apns;
- mBase = base;
- mRowId = rowId;
- mCurrent = current;
- }
-
- @Override
- public String getMmsc() {
- return mBase.getMmsc();
- }
-
- @Override
- public String getMmsProxy() {
- return mBase.getMmsProxy();
- }
-
- @Override
- public int getMmsProxyPort() {
- return mBase.getMmsProxyPort();
- }
-
- @Override
- public void setSuccess() {
- moveToListHead();
- setCurrentInDatabase();
- }
-
- /**
- * Try to move this APN to the head of in-memory list
- */
- private void moveToListHead() {
- // If this is being marked as a successful APN, move it to the top of the list so
- // next time it will be tried first
- boolean moved = false;
- synchronized (mApns) {
- if (mApns.get(0) != this) {
- mApns.remove(this);
- mApns.add(0, this);
- moved = true;
- }
- }
- if (moved) {
- LogUtil.d(LogUtil.BUGLE_TAG, "Set APN ["
- + "MMSC=" + getMmsc() + ", "
- + "PROXY=" + getMmsProxy() + ", "
- + "PORT=" + getMmsProxyPort() + "] to be first");
- }
- }
-
- /**
- * Try to set the APN to be CURRENT in its database table
- */
- private void setCurrentInDatabase() {
- synchronized (this) {
- if (mCurrent > 0) {
- // Already current
- return;
- }
- mCurrent = 1;
- }
- LogUtil.d(LogUtil.BUGLE_TAG, "Set APN @" + mRowId + " to be CURRENT in local db");
- final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
- database.beginTransaction();
- try {
- // clear the previous current=1 apn
- // we don't clear current=2 apn since it is manually selected by user
- // and we should not override it.
- database.update(ApnDatabase.APN_TABLE, CURRENT_NULL_VALUE,
- CLEAR_UPDATE_SELECTION, CLEAR_UPDATE_SELECTION_ARGS);
- // set this one to be current (1)
- database.update(ApnDatabase.APN_TABLE, CURRENT_SET_VALUE, SET_UPDATE_SELECTION,
- new String[] { Long.toString(mRowId) });
- database.setTransactionSuccessful();
- } finally {
- database.endTransaction();
- }
- }
-
- public boolean equals(final BaseApn other) {
- if (other == null) {
- return false;
- }
- return mBase.equals(other);
- }
- }
-
- /**
- * APN_TYPE_ALL is a special type to indicate that this APN entry can
- * service all data connections.
- */
- public static final String APN_TYPE_ALL = "*";
- /** APN type for MMS traffic */
- public static final String APN_TYPE_MMS = "mms";
-
- private static final String[] APN_PROJECTION_SYSTEM = {
- Telephony.Carriers.TYPE,
- Telephony.Carriers.MMSC,
- Telephony.Carriers.MMSPROXY,
- Telephony.Carriers.MMSPORT,
- };
- private static final String[] APN_PROJECTION_LOCAL = {
- Telephony.Carriers.TYPE,
- Telephony.Carriers.MMSC,
- Telephony.Carriers.MMSPROXY,
- Telephony.Carriers.MMSPORT,
- Telephony.Carriers.CURRENT,
- Telephony.Carriers._ID,
- };
- private static final int COLUMN_TYPE = 0;
- private static final int COLUMN_MMSC = 1;
- private static final int COLUMN_MMSPROXY = 2;
- private static final int COLUMN_MMSPORT = 3;
- private static final int COLUMN_CURRENT = 4;
- private static final int COLUMN_ID = 5;
-
- private static final String SELECTION_APN = Telephony.Carriers.APN + "=?";
- private static final String SELECTION_CURRENT = Telephony.Carriers.CURRENT + " IS NOT NULL";
- private static final String SELECTION_NUMERIC = Telephony.Carriers.NUMERIC + "=?";
- private static final String ORDER_BY = Telephony.Carriers.CURRENT + " DESC";
-
- private final Context mContext;
-
- // Cached APNs for subIds
- private final SparseArray<List<ApnSettingsLoader.Apn>> mApnsCache;
-
- public BugleApnSettingsLoader(final Context context) {
- mContext = context;
- mApnsCache = new SparseArray<>();
- }
-
- @Override
- public List<ApnSettingsLoader.Apn> get(final String apnName) {
- final int subId = PhoneUtils.getDefault().getEffectiveSubId(
- ParticipantData.DEFAULT_SELF_SUB_ID);
- List<ApnSettingsLoader.Apn> apns;
- boolean didLoad = false;
- synchronized (this) {
- apns = mApnsCache.get(subId);
- if (apns == null) {
- apns = new ArrayList<>();
- mApnsCache.put(subId, apns);
- loadLocked(subId, apnName, apns);
- didLoad = true;
- }
- }
- if (didLoad) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Loaded " + apns.size() + " APNs");
- }
- return apns;
- }
-
- private void loadLocked(final int subId, final String apnName, final List<Apn> apns) {
- // Try Gservices first
- loadFromGservices(apns);
- if (apns.size() > 0) {
- return;
- }
- // Try system APN table
- loadFromSystem(subId, apnName, apns);
- if (apns.size() > 0) {
- return;
- }
- // Try local APN table
- loadFromLocalDatabase(apnName, apns);
- if (apns.size() <= 0) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Failed to load any APN");
- }
- }
-
- /**
- * Load from Gservices if APN setting is set in Gservices
- *
- * @param apns the list used to return results
- */
- private void loadFromGservices(final List<Apn> apns) {
- final BugleGservices gservices = BugleGservices.get();
- final String mmsc = gservices.getString(BugleGservicesKeys.MMS_MMSC, null);
- if (TextUtils.isEmpty(mmsc)) {
- return;
- }
- LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from gservices");
- final String proxy = gservices.getString(BugleGservicesKeys.MMS_PROXY_ADDRESS, null);
- final int port = gservices.getInt(BugleGservicesKeys.MMS_PROXY_PORT, -1);
- final Apn apn = BaseApn.from("mms", mmsc, proxy, Integer.toString(port));
- if (apn != null) {
- apns.add(apn);
- }
- }
-
- /**
- * Load matching APNs from telephony provider.
- * We try different combinations of the query to work around some platform quirks.
- *
- * @param subId the SIM subId
- * @param apnName the APN name to match
- * @param apns the list used to return results
- */
- private void loadFromSystem(final int subId, final String apnName, final List<Apn> apns) {
- Uri uri;
- if (OsUtil.isAtLeastL_MR1() && subId != MmsManager.DEFAULT_SUB_ID) {
- uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId);
- } else {
- uri = Telephony.Carriers.CONTENT_URI;
- }
- Cursor cursor = null;
- try {
- for (; ; ) {
- // Try different combinations of queries. Some would work on some platforms.
- // So we query each combination until we find one returns non-empty result.
- cursor = querySystem(uri, true/*checkCurrent*/, apnName);
- if (cursor != null) {
- break;
- }
- cursor = querySystem(uri, false/*checkCurrent*/, apnName);
- if (cursor != null) {
- break;
- }
- cursor = querySystem(uri, true/*checkCurrent*/, null/*apnName*/);
- if (cursor != null) {
- break;
- }
- cursor = querySystem(uri, false/*checkCurrent*/, null/*apnName*/);
- break;
- }
- } catch (final SecurityException e) {
- // Can't access platform APN table, return directly
- return;
- }
- if (cursor == null) {
- return;
- }
- try {
- if (cursor.moveToFirst()) {
- final ApnSettingsLoader.Apn apn = BaseApn.from(
- cursor.getString(COLUMN_TYPE),
- cursor.getString(COLUMN_MMSC),
- cursor.getString(COLUMN_MMSPROXY),
- cursor.getString(COLUMN_MMSPORT));
- if (apn != null) {
- apns.add(apn);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Query system APN table
- *
- * @param uri The APN query URL to use
- * @param checkCurrent If add "CURRENT IS NOT NULL" condition
- * @param apnName The optional APN name for query condition
- * @return A cursor of the query result. If a cursor is returned as not null, it is
- * guaranteed to contain at least one row.
- */
- private Cursor querySystem(final Uri uri, final boolean checkCurrent, String apnName) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from system, "
- + "checkCurrent=" + checkCurrent + " apnName=" + apnName);
- final StringBuilder selectionBuilder = new StringBuilder();
- String[] selectionArgs = null;
- if (checkCurrent) {
- selectionBuilder.append(SELECTION_CURRENT);
- }
- apnName = trimWithNullCheck(apnName);
- if (!TextUtils.isEmpty(apnName)) {
- if (selectionBuilder.length() > 0) {
- selectionBuilder.append(" AND ");
- }
- selectionBuilder.append(SELECTION_APN);
- selectionArgs = new String[] { apnName };
- }
- try {
- final Cursor cursor = SqliteWrapper.query(
- mContext,
- mContext.getContentResolver(),
- uri,
- APN_PROJECTION_SYSTEM,
- selectionBuilder.toString(),
- selectionArgs,
- null/*sortOrder*/);
- if (cursor == null || cursor.getCount() < 1) {
- if (cursor != null) {
- cursor.close();
- }
- LogUtil.w(LogUtil.BUGLE_TAG, "Query " + uri + " with apn " + apnName + " and "
- + (checkCurrent ? "checking CURRENT" : "not checking CURRENT")
- + " returned empty");
- return null;
- }
- return cursor;
- } catch (final SQLiteException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "APN table query exception: " + e);
- } catch (final SecurityException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Platform restricts APN table access: " + e);
- throw e;
- }
- return null;
- }
-
- /**
- * Load matching APNs from local APN table.
- * We try both using the APN name and not using the APN name.
- *
- * @param apnName the APN name
- * @param apns the list of results to return
- */
- private void loadFromLocalDatabase(final String apnName, final List<Apn> apns) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from local APN table");
- final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
- final String mccMnc = PhoneUtils.getMccMncString(PhoneUtils.getDefault().getMccMnc());
- Cursor cursor = null;
- cursor = queryLocalDatabase(database, mccMnc, apnName);
- if (cursor == null) {
- cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/);
- }
- if (cursor == null) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Could not find any APN in local table");
- return;
- }
- try {
- while (cursor.moveToNext()) {
- final Apn apn = DatabaseApn.from(apns,
- cursor.getString(COLUMN_TYPE),
- cursor.getString(COLUMN_MMSC),
- cursor.getString(COLUMN_MMSPROXY),
- cursor.getString(COLUMN_MMSPORT),
- cursor.getLong(COLUMN_ID),
- cursor.getInt(COLUMN_CURRENT));
- if (apn != null) {
- apns.add(apn);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Make a query of local APN table based on MCC/MNC and APN name, sorted by CURRENT
- * column in descending order
- *
- * @param db the local database
- * @param numeric the MCC/MNC string
- * @param apnName the optional APN name to match
- * @return the cursor of the query, null if no result
- */
- private static Cursor queryLocalDatabase(final SQLiteDatabase db, final String numeric,
- final String apnName) {
- final String selection;
- final String[] selectionArgs;
- if (TextUtils.isEmpty(apnName)) {
- selection = SELECTION_NUMERIC;
- selectionArgs = new String[] { numeric };
- } else {
- selection = SELECTION_NUMERIC + " AND " + SELECTION_APN;
- selectionArgs = new String[] { numeric, apnName };
- }
- Cursor cursor = null;
- try {
- cursor = db.query(ApnDatabase.APN_TABLE, APN_PROJECTION_LOCAL, selection, selectionArgs,
- null/*groupBy*/, null/*having*/, ORDER_BY, null/*limit*/);
- } catch (final SQLiteException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Local APN table does not exist. Try rebuilding.", e);
- ApnDatabase.forceBuildAndLoadApnTables();
- cursor = db.query(ApnDatabase.APN_TABLE, APN_PROJECTION_LOCAL, selection, selectionArgs,
- null/*groupBy*/, null/*having*/, ORDER_BY, null/*limit*/);
- }
- if (cursor == null || cursor.getCount() < 1) {
- if (cursor != null) {
- cursor.close();
- }
- LogUtil.w(LogUtil.BUGLE_TAG, "Query local APNs with apn " + apnName
- + " returned empty");
- return null;
- }
- return cursor;
- }
-
- private static String trimWithNullCheck(final String value) {
- return value != null ? value.trim() : null;
- }
-
- /**
- * Trim leading zeros from IPv4 address strings
- * Our base libraries will interpret that as octel..
- * Must leave non v4 addresses and host names alone.
- * For example, 192.168.000.010 -> 192.168.0.10
- *
- * @param addr a string representing an ip addr
- * @return a string propertly trimmed
- */
- private static String trimV4AddrZeros(final String addr) {
- if (addr == null) {
- return null;
- }
- final String[] octets = addr.split("\\.");
- if (octets.length != 4) {
- return addr;
- }
- final StringBuilder builder = new StringBuilder(16);
- String result = null;
- for (int i = 0; i < 4; i++) {
- try {
- if (octets[i].length() > 3) {
- return addr;
- }
- builder.append(Integer.parseInt(octets[i]));
- } catch (final NumberFormatException e) {
- return addr;
- }
- if (i < 3) {
- builder.append('.');
- }
- }
- result = builder.toString();
- return result;
- }
-
- /**
- * Check if the APN contains the APN type we want
- *
- * @param types The string encodes a list of supported types
- * @param requestType The type we want
- * @return true if the input types string contains the requestType
- */
- public static boolean isValidApnType(final String types, final String requestType) {
- // If APN type is unspecified, assume APN_TYPE_ALL.
- if (TextUtils.isEmpty(types)) {
- return true;
- }
- for (final String t : types.split(",")) {
- if (t.equals(requestType) || t.equals(APN_TYPE_ALL)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Get the ID of first APN to try
- */
- public static String getFirstTryApn(final SQLiteDatabase database, final String mccMnc) {
- String key = null;
- Cursor cursor = null;
- try {
- cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/);
- if (cursor.moveToFirst()) {
- key = cursor.getString(ApnDatabase.COLUMN_ID);
- }
- } catch (final Exception e) {
- // Nothing to do
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return key;
- }
-}
diff --git a/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java b/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java
deleted file mode 100644
index ac6c7e4..0000000
--- a/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.os.Bundle;
-import android.support.v7.mms.CarrierConfigValuesLoader;
-import android.util.SparseArray;
-
-import com.android.messaging.R;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Carrier configuration loader
- *
- * Loader tries to load from resources. If there is MMS API available, also
- * load from system.
- */
-public class BugleCarrierConfigValuesLoader implements CarrierConfigValuesLoader {
- /*
- * Key types
- */
- public static final String KEY_TYPE_INT = "int";
- public static final String KEY_TYPE_BOOL = "bool";
- public static final String KEY_TYPE_STRING = "string";
-
- private final Context mContext;
-
- // Cached values for subIds
- private final SparseArray<Bundle> mValuesCache;
-
- public BugleCarrierConfigValuesLoader(final Context context) {
- mContext = context;
- mValuesCache = new SparseArray<>();
- }
-
- @Override
- public Bundle get(int subId) {
- subId = PhoneUtils.getDefault().getEffectiveSubId(subId);
- Bundle values;
- String loadSource = null;
- synchronized (this) {
- values = mValuesCache.get(subId);
- if (values == null) {
- values = new Bundle();
- mValuesCache.put(subId, values);
- loadSource = loadLocked(subId, values);
- }
- }
- if (loadSource != null) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Carrier configs loaded: " + values
- + " from " + loadSource + " for subId=" + subId);
- }
- return values;
- }
-
- /**
- * Clear the cache for reloading
- */
- public void reset() {
- synchronized (this) {
- mValuesCache.clear();
- }
- }
-
- /**
- * Loading carrier config values
- *
- * @param subId which SIM to load for
- * @param values the result to add to
- * @return the source of the config, could be "resources" or "resources+system"
- */
- private String loadLocked(final int subId, final Bundle values) {
- // Load from resources in earlier platform
- loadFromResources(subId, values);
- if (OsUtil.isAtLeastL()) {
- // Load from system to override if system API exists
- loadFromSystem(subId, values);
- return "resources+system";
- }
- return "resources";
- }
-
- /**
- * Load from system, using MMS API
- *
- * @param subId which SIM to load for
- * @param values the result to add to
- */
- private static void loadFromSystem(final int subId, final Bundle values) {
- try {
- final Bundle systemValues =
- PhoneUtils.get(subId).getSmsManager().getCarrierConfigValues();
- if (systemValues != null) {
- values.putAll(systemValues);
- }
- } catch (final Exception e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Calling system getCarrierConfigValues exception", e);
- }
- }
-
- /**
- * Load from SIM-dependent resources
- *
- * @param subId which SIM to load for
- * @param values the result to add to
- */
- private void loadFromResources(final int subId, final Bundle values) {
- // Get a subscription-dependent context for loading the mms_config.xml
- final Context subContext = getSubDepContext(mContext, subId);
- // Load and parse the XML
- XmlResourceParser parser = null;
- try {
- parser = subContext.getResources().getXml(R.xml.mms_config);
- final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser);
- processor.setMmsConfigHandler(new ApnsXmlProcessor.MmsConfigHandler() {
- @Override
- public void process(final String mccMnc, final String key, final String value,
- final String type) {
- update(values, type, key, value);
- }
- });
- processor.process();
- } catch (final Resources.NotFoundException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Can not find mms_config.xml");
- } finally {
- if (parser != null) {
- parser.close();
- }
- }
- }
-
- /**
- * Get a subscription's Context so we can load resources from it
- *
- * @param context the sub-independent Context
- * @param subId the SIM's subId
- * @return the sub-dependent Context
- */
- private static Context getSubDepContext(final Context context, final int subId) {
- if (!OsUtil.isAtLeastL_MR1()) {
- return context;
- }
- final int[] mccMnc = PhoneUtils.get(subId).getMccMnc();
- final int mcc = mccMnc[0];
- final int mnc = mccMnc[1];
- final Configuration subConfig = new Configuration();
- if (mcc == 0 && mnc == 0) {
- Configuration config = context.getResources().getConfiguration();
- subConfig.mcc = config.mcc;
- subConfig.mnc = config.mnc;
- } else {
- subConfig.mcc = mcc;
- subConfig.mnc = mnc;
- }
- return context.createConfigurationContext(subConfig);
- }
-
- /**
- * Add or update a carrier config key/value pair to the Bundle
- *
- * @param values the result Bundle to add to
- * @param type the value type
- * @param key the key
- * @param value the value
- */
- public static void update(final Bundle values, final String type, final String key,
- final String value) {
- try {
- if (KEY_TYPE_INT.equals(type)) {
- values.putInt(key, Integer.parseInt(value));
- } else if (KEY_TYPE_BOOL.equals(type)) {
- values.putBoolean(key, Boolean.parseBoolean(value));
- } else if (KEY_TYPE_STRING.equals(type)){
- values.putString(key, value);
- }
- } catch (final NumberFormatException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Add carrier values: "
- + "invalid " + key + "," + value + "," + type);
- }
- }
-}
diff --git a/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java b/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java
deleted file mode 100644
index a8dc5c4..0000000
--- a/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.Context;
-import android.support.v7.mms.UserAgentInfoLoader;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.VersionUtil;
-
-/**
- * User agent and UA profile URL loader
- */
-public class BugleUserAgentInfoLoader implements UserAgentInfoLoader {
- private static final String DEFAULT_USER_AGENT_PREFIX = "Bugle/";
-
- private Context mContext;
- private boolean mLoaded;
-
- private String mUserAgent;
- private String mUAProfUrl;
-
- public BugleUserAgentInfoLoader(final Context context) {
- mContext = context;
- }
-
- @Override
- public String getUserAgent() {
- load();
- return mUserAgent;
- }
-
- @Override
- public String getUAProfUrl() {
- load();
- return mUAProfUrl;
- }
-
- private void load() {
- if (mLoaded) {
- return;
- }
- boolean didLoad = false;
- synchronized (this) {
- if (!mLoaded) {
- loadLocked();
- mLoaded = true;
- didLoad = true;
- }
- }
- if (didLoad) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Loaded user agent info: "
- + "UA=" + mUserAgent + ", UAProfUrl=" + mUAProfUrl);
- }
- }
-
- private void loadLocked() {
- if (OsUtil.isAtLeastKLP()) {
- // load the MMS User agent and UaProfUrl from TelephonyManager APIs
- final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
- mUserAgent = telephonyManager.getMmsUserAgent();
- mUAProfUrl = telephonyManager.getMmsUAProfUrl();
- }
- // if user agent string isn't set, use the format "Bugle/<app_version>".
- if (TextUtils.isEmpty(mUserAgent)) {
- final String simpleVersionName = VersionUtil.getInstance(mContext).getSimpleName();
- mUserAgent = DEFAULT_USER_AGENT_PREFIX + simpleVersionName;
- }
- // if the UAProfUrl isn't set, get it from Gservices
- if (TextUtils.isEmpty(mUAProfUrl)) {
- mUAProfUrl = BugleGservices.get().getString(
- BugleGservicesKeys.MMS_UA_PROFILE_URL,
- BugleGservicesKeys.MMS_UA_PROFILE_URL_DEFAULT);
- }
- }
-}
diff --git a/src/com/android/messaging/sms/DatabaseMessages.java b/src/com/android/messaging/sms/DatabaseMessages.java
deleted file mode 100644
index 0f662e5..0000000
--- a/src/com/android/messaging/sms/DatabaseMessages.java
+++ /dev/null
@@ -1,1006 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.text.TextUtils;
-import android.util.Log;
-import android.webkit.MimeTypeMap;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.media.VideoThumbnailRequest;
-import com.android.messaging.mmslib.pdu.CharacterSets;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaMetadataRetrieverWrapper;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.collect.Lists;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class contains various SMS/MMS database entities from telephony provider
- */
-public class DatabaseMessages {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- public abstract static class DatabaseMessage {
- public abstract int getProtocol();
- public abstract String getUri();
- public abstract long getTimestampInMillis();
-
- @Override
- public boolean equals(final Object other) {
- if (other == null || !(other instanceof DatabaseMessage)) {
- return false;
- }
- final DatabaseMessage otherDbMsg = (DatabaseMessage) other;
- // No need to check timestamp since we only need this when we compare
- // messages at the same timestamp
- return TextUtils.equals(getUri(), otherDbMsg.getUri());
- }
-
- @Override
- public int hashCode() {
- // No need to check timestamp since we only need this when we compare
- // messages at the same timestamp
- return getUri().hashCode();
- }
- }
-
- /**
- * SMS message
- */
- public static class SmsMessage extends DatabaseMessage implements Parcelable {
- private static int sIota = 0;
- public static final int INDEX_ID = sIota++;
- public static final int INDEX_TYPE = sIota++;
- public static final int INDEX_ADDRESS = sIota++;
- public static final int INDEX_BODY = sIota++;
- public static final int INDEX_DATE = sIota++;
- public static final int INDEX_THREAD_ID = sIota++;
- public static final int INDEX_STATUS = sIota++;
- public static final int INDEX_READ = sIota++;
- public static final int INDEX_SEEN = sIota++;
- public static final int INDEX_DATE_SENT = sIota++;
- public static final int INDEX_SUB_ID = sIota++;
-
- private static String[] sProjection;
-
- public static String[] getProjection() {
- if (sProjection == null) {
- String[] projection = new String[] {
- Sms._ID,
- Sms.TYPE,
- Sms.ADDRESS,
- Sms.BODY,
- Sms.DATE,
- Sms.THREAD_ID,
- Sms.STATUS,
- Sms.READ,
- Sms.SEEN,
- Sms.DATE_SENT,
- Sms.SUBSCRIPTION_ID,
- };
- if (!MmsUtils.hasSmsDateSentColumn()) {
- projection[INDEX_DATE_SENT] = Sms.DATE;
- }
- if (!OsUtil.isAtLeastL_MR1()) {
- Assert.equals(INDEX_SUB_ID, projection.length - 1);
- String[] withoutSubId = new String[projection.length - 1];
- System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length);
- projection = withoutSubId;
- }
-
- sProjection = projection;
- }
-
- return sProjection;
- }
-
- public String mUri;
- public String mAddress;
- public String mBody;
- private long mRowId;
- public long mTimestampInMillis;
- public long mTimestampSentInMillis;
- public int mType;
- public long mThreadId;
- public int mStatus;
- public boolean mRead;
- public boolean mSeen;
- public int mSubId;
-
- private SmsMessage() {
- }
-
- /**
- * Load from a cursor of a query that returns the SMS to import
- *
- * @param cursor
- */
- private void load(final Cursor cursor) {
- mRowId = cursor.getLong(INDEX_ID);
- mAddress = cursor.getString(INDEX_ADDRESS);
- mBody = cursor.getString(INDEX_BODY);
- mTimestampInMillis = cursor.getLong(INDEX_DATE);
- // Before ICS, there is no "date_sent" so use copy of "date" value
- mTimestampSentInMillis = cursor.getLong(INDEX_DATE_SENT);
- mType = cursor.getInt(INDEX_TYPE);
- mThreadId = cursor.getLong(INDEX_THREAD_ID);
- mStatus = cursor.getInt(INDEX_STATUS);
- mRead = cursor.getInt(INDEX_READ) == 0 ? false : true;
- mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true;
- mUri = ContentUris.withAppendedId(Sms.CONTENT_URI, mRowId).toString();
- mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID);
- }
-
- /**
- * Get a new SmsMessage by loading from the cursor of a query
- * that returns the SMS to import
- *
- * @param cursor
- * @return
- */
- public static SmsMessage get(final Cursor cursor) {
- final SmsMessage msg = new SmsMessage();
- msg.load(cursor);
- return msg;
- }
-
- @Override
- public String getUri() {
- return mUri;
- }
-
- public int getSubId() {
- return mSubId;
- }
-
- @Override
- public int getProtocol() {
- return MessageData.PROTOCOL_SMS;
- }
-
- @Override
- public long getTimestampInMillis() {
- return mTimestampInMillis;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- private SmsMessage(final Parcel in) {
- mUri = in.readString();
- mRowId = in.readLong();
- mTimestampInMillis = in.readLong();
- mTimestampSentInMillis = in.readLong();
- mType = in.readInt();
- mThreadId = in.readLong();
- mStatus = in.readInt();
- mRead = in.readInt() != 0;
- mSeen = in.readInt() != 0;
- mSubId = in.readInt();
-
- // SMS specific
- mAddress = in.readString();
- mBody = in.readString();
- }
-
- public static final Parcelable.Creator<SmsMessage> CREATOR
- = new Parcelable.Creator<SmsMessage>() {
- @Override
- public SmsMessage createFromParcel(final Parcel in) {
- return new SmsMessage(in);
- }
-
- @Override
- public SmsMessage[] newArray(final int size) {
- return new SmsMessage[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- out.writeString(mUri);
- out.writeLong(mRowId);
- out.writeLong(mTimestampInMillis);
- out.writeLong(mTimestampSentInMillis);
- out.writeInt(mType);
- out.writeLong(mThreadId);
- out.writeInt(mStatus);
- out.writeInt(mRead ? 1 : 0);
- out.writeInt(mSeen ? 1 : 0);
- out.writeInt(mSubId);
-
- // SMS specific
- out.writeString(mAddress);
- out.writeString(mBody);
- }
- }
-
- /**
- * MMS message
- */
- public static class MmsMessage extends DatabaseMessage implements Parcelable {
- private static int sIota = 0;
- public static final int INDEX_ID = sIota++;
- public static final int INDEX_MESSAGE_BOX = sIota++;
- public static final int INDEX_SUBJECT = sIota++;
- public static final int INDEX_SUBJECT_CHARSET = sIota++;
- public static final int INDEX_MESSAGE_SIZE = sIota++;
- public static final int INDEX_DATE = sIota++;
- public static final int INDEX_DATE_SENT = sIota++;
- public static final int INDEX_THREAD_ID = sIota++;
- public static final int INDEX_PRIORITY = sIota++;
- public static final int INDEX_STATUS = sIota++;
- public static final int INDEX_READ = sIota++;
- public static final int INDEX_SEEN = sIota++;
- public static final int INDEX_CONTENT_LOCATION = sIota++;
- public static final int INDEX_TRANSACTION_ID = sIota++;
- public static final int INDEX_MESSAGE_TYPE = sIota++;
- public static final int INDEX_EXPIRY = sIota++;
- public static final int INDEX_RESPONSE_STATUS = sIota++;
- public static final int INDEX_RETRIEVE_STATUS = sIota++;
- public static final int INDEX_SUB_ID = sIota++;
-
- private static String[] sProjection;
-
- public static String[] getProjection() {
- if (sProjection == null) {
- String[] projection = new String[] {
- Mms._ID,
- Mms.MESSAGE_BOX,
- Mms.SUBJECT,
- Mms.SUBJECT_CHARSET,
- Mms.MESSAGE_SIZE,
- Mms.DATE,
- Mms.DATE_SENT,
- Mms.THREAD_ID,
- Mms.PRIORITY,
- Mms.STATUS,
- Mms.READ,
- Mms.SEEN,
- Mms.CONTENT_LOCATION,
- Mms.TRANSACTION_ID,
- Mms.MESSAGE_TYPE,
- Mms.EXPIRY,
- Mms.RESPONSE_STATUS,
- Mms.RETRIEVE_STATUS,
- Mms.SUBSCRIPTION_ID,
- };
-
- if (!OsUtil.isAtLeastL_MR1()) {
- Assert.equals(INDEX_SUB_ID, projection.length - 1);
- String[] withoutSubId = new String[projection.length - 1];
- System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length);
- projection = withoutSubId;
- }
-
- sProjection = projection;
- }
-
- return sProjection;
- }
-
- public String mUri;
- private long mRowId;
- public int mType;
- public String mSubject;
- public int mSubjectCharset;
- private long mSize;
- public long mTimestampInMillis;
- public long mSentTimestampInMillis;
- public long mThreadId;
- public int mPriority;
- public int mStatus;
- public boolean mRead;
- public boolean mSeen;
- public String mContentLocation;
- public String mTransactionId;
- public int mMmsMessageType;
- public long mExpiryInMillis;
- public int mSubId;
- public String mSender;
- public int mResponseStatus;
- public int mRetrieveStatus;
-
- public List<MmsPart> mParts = Lists.newArrayList();
- private boolean mPartsProcessed = false;
-
- private MmsMessage() {
- }
-
- /**
- * Load from a cursor of a query that returns the MMS to import
- *
- * @param cursor
- */
- public void load(final Cursor cursor) {
- mRowId = cursor.getLong(INDEX_ID);
- mType = cursor.getInt(INDEX_MESSAGE_BOX);
- mSubject = cursor.getString(INDEX_SUBJECT);
- mSubjectCharset = cursor.getInt(INDEX_SUBJECT_CHARSET);
- if (!TextUtils.isEmpty(mSubject)) {
- // PduPersister stores the subject using ISO_8859_1
- // Let's load it using that encoding and convert it back to its original
- // See PduPersister.persist and PduPersister.toIsoString
- // (Refer to bug b/11162476)
- mSubject = getDecodedString(
- getStringBytes(mSubject, CharacterSets.ISO_8859_1), mSubjectCharset);
- }
- mSize = cursor.getLong(INDEX_MESSAGE_SIZE);
- // MMS db times are in seconds
- mTimestampInMillis = cursor.getLong(INDEX_DATE) * 1000;
- mSentTimestampInMillis = cursor.getLong(INDEX_DATE_SENT) * 1000;
- mThreadId = cursor.getLong(INDEX_THREAD_ID);
- mPriority = cursor.getInt(INDEX_PRIORITY);
- mStatus = cursor.getInt(INDEX_STATUS);
- mRead = cursor.getInt(INDEX_READ) == 0 ? false : true;
- mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true;
- mContentLocation = cursor.getString(INDEX_CONTENT_LOCATION);
- mTransactionId = cursor.getString(INDEX_TRANSACTION_ID);
- mMmsMessageType = cursor.getInt(INDEX_MESSAGE_TYPE);
- mExpiryInMillis = cursor.getLong(INDEX_EXPIRY) * 1000;
- mResponseStatus = cursor.getInt(INDEX_RESPONSE_STATUS);
- mRetrieveStatus = cursor.getInt(INDEX_RETRIEVE_STATUS);
- // Clear all parts in case we reuse this object
- mParts.clear();
- mPartsProcessed = false;
- mUri = ContentUris.withAppendedId(Mms.CONTENT_URI, mRowId).toString();
- mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID);
- }
-
- /**
- * Get a new MmsMessage by loading from the cursor of a query
- * that returns the MMS to import
- *
- * @param cursor
- * @return
- */
- public static MmsMessage get(final Cursor cursor) {
- final MmsMessage msg = new MmsMessage();
- msg.load(cursor);
- return msg;
- }
- /**
- * Add a loaded MMS part
- *
- * @param part
- */
- public void addPart(final MmsPart part) {
- mParts.add(part);
- }
-
- public List<MmsPart> getParts() {
- return mParts;
- }
-
- public long getSize() {
- if (!mPartsProcessed) {
- processParts();
- }
- return mSize;
- }
-
- /**
- * Process loaded MMS parts to obtain the combined text, the combined attachment url,
- * the combined content type and the combined size.
- */
- private void processParts() {
- if (mPartsProcessed) {
- return;
- }
- mPartsProcessed = true;
- // Remember the width and height of the first media part
- // These are needed when building attachment list
- long sizeOfParts = 0L;
- for (final MmsPart part : mParts) {
- sizeOfParts += part.mSize;
- }
- if (mSize <= 0) {
- mSize = mSubject != null ? mSubject.getBytes().length : 0L;
- mSize += sizeOfParts;
- }
- }
-
- @Override
- public String getUri() {
- return mUri;
- }
-
- public long getId() {
- return mRowId;
- }
-
- public int getSubId() {
- return mSubId;
- }
-
- @Override
- public int getProtocol() {
- return MessageData.PROTOCOL_MMS;
- }
-
- @Override
- public long getTimestampInMillis() {
- return mTimestampInMillis;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public void setSender(final String sender) {
- mSender = sender;
- }
-
- private MmsMessage(final Parcel in) {
- mUri = in.readString();
- mRowId = in.readLong();
- mTimestampInMillis = in.readLong();
- mSentTimestampInMillis = in.readLong();
- mType = in.readInt();
- mThreadId = in.readLong();
- mStatus = in.readInt();
- mRead = in.readInt() != 0;
- mSeen = in.readInt() != 0;
- mSubId = in.readInt();
-
- // MMS specific
- mSubject = in.readString();
- mContentLocation = in.readString();
- mTransactionId = in.readString();
- mSender = in.readString();
-
- mSize = in.readLong();
- mExpiryInMillis = in.readLong();
-
- mSubjectCharset = in.readInt();
- mPriority = in.readInt();
- mMmsMessageType = in.readInt();
- mResponseStatus = in.readInt();
- mRetrieveStatus = in.readInt();
-
- final int nParts = in.readInt();
- mParts = new ArrayList<MmsPart>();
- mPartsProcessed = false;
- for (int i = 0; i < nParts; i++) {
- mParts.add((MmsPart) in.readParcelable(getClass().getClassLoader()));
- }
- }
-
- public static final Parcelable.Creator<MmsMessage> CREATOR
- = new Parcelable.Creator<MmsMessage>() {
- @Override
- public MmsMessage createFromParcel(final Parcel in) {
- return new MmsMessage(in);
- }
-
- @Override
- public MmsMessage[] newArray(final int size) {
- return new MmsMessage[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- out.writeString(mUri);
- out.writeLong(mRowId);
- out.writeLong(mTimestampInMillis);
- out.writeLong(mSentTimestampInMillis);
- out.writeInt(mType);
- out.writeLong(mThreadId);
- out.writeInt(mStatus);
- out.writeInt(mRead ? 1 : 0);
- out.writeInt(mSeen ? 1 : 0);
- out.writeInt(mSubId);
-
- out.writeString(mSubject);
- out.writeString(mContentLocation);
- out.writeString(mTransactionId);
- out.writeString(mSender);
-
- out.writeLong(mSize);
- out.writeLong(mExpiryInMillis);
-
- out.writeInt(mSubjectCharset);
- out.writeInt(mPriority);
- out.writeInt(mMmsMessageType);
- out.writeInt(mResponseStatus);
- out.writeInt(mRetrieveStatus);
-
- out.writeInt(mParts.size());
- for (final MmsPart part : mParts) {
- out.writeParcelable(part, 0);
- }
- }
- }
-
- /**
- * Part of an MMS message
- */
- public static class MmsPart implements Parcelable {
- public static final String[] PROJECTION = new String[] {
- Mms.Part._ID,
- Mms.Part.MSG_ID,
- Mms.Part.CHARSET,
- Mms.Part.CONTENT_TYPE,
- Mms.Part.TEXT,
- };
- private static int sIota = 0;
- public static final int INDEX_ID = sIota++;
- public static final int INDEX_MSG_ID = sIota++;
- public static final int INDEX_CHARSET = sIota++;
- public static final int INDEX_CONTENT_TYPE = sIota++;
- public static final int INDEX_TEXT = sIota++;
-
- public String mUri;
- public long mRowId;
- public long mMessageId;
- public String mContentType;
- public String mText;
- public int mCharset;
- private int mWidth;
- private int mHeight;
- public long mSize;
-
- private MmsPart() {
- }
-
- /**
- * Load from a cursor of a query that returns the MMS part to import
- *
- * @param cursor
- */
- public void load(final Cursor cursor, final boolean loadMedia) {
- mRowId = cursor.getLong(INDEX_ID);
- mMessageId = cursor.getLong(INDEX_MSG_ID);
- mContentType = cursor.getString(INDEX_CONTENT_TYPE);
- mText = cursor.getString(INDEX_TEXT);
- mCharset = cursor.getInt(INDEX_CHARSET);
- mWidth = 0;
- mHeight = 0;
- mSize = 0;
- if (isMedia()) {
- // For importing we don't load media since performance is critical
- // For loading when we receive mms, we do load media to get enough
- // information of the media file
- if (loadMedia) {
- if (ContentType.isImageType(mContentType)) {
- loadImage();
- } else if (ContentType.isVideoType(mContentType)) {
- loadVideo();
- } // No need to load audio for parsing
- mSize = MmsUtils.getMediaFileSize(getDataUri());
- }
- } else {
- // Load text if not media type
- loadText();
- }
- mUri = Uri.withAppendedPath(Mms.CONTENT_URI, cursor.getString(INDEX_ID)).toString();
- }
-
- /**
- * Get content type from file extension
- */
- private static String extractContentType(final Context context, final Uri uri) {
- final String path = uri.getPath();
- final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
- String extension = MimeTypeMap.getFileExtensionFromUrl(path);
- if (TextUtils.isEmpty(extension)) {
- // getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle
- // urlEncoded strings. Let's try one last time at finding the extension.
- final int dotPos = path.lastIndexOf('.');
- if (0 <= dotPos) {
- extension = path.substring(dotPos + 1);
- }
- }
- return mimeTypeMap.getMimeTypeFromExtension(extension);
- }
-
- /**
- * Get text of a text part
- */
- private void loadText() {
- byte[] data = null;
- if (isEmbeddedTextType()) {
- // Embedded text, get from the "text" column
- if (!TextUtils.isEmpty(mText)) {
- data = getStringBytes(mText, mCharset);
- }
- } else {
- // Not embedded, load from disk
- final ContentResolver resolver =
- Factory.get().getApplicationContext().getContentResolver();
- final Uri uri = getDataUri();
- InputStream is = null;
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- is = resolver.openInputStream(uri);
- final byte[] buffer = new byte[256];
- int len = is.read(buffer);
- while (len >= 0) {
- baos.write(buffer, 0, len);
- len = is.read(buffer);
- }
- } catch (final IOException e) {
- LogUtil.e(TAG,
- "DatabaseMessages.MmsPart: loading text from file failed: " + e, e);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- LogUtil.e(TAG, "DatabaseMessages.MmsPart: close file failed: " + e, e);
- }
- }
- }
- data = baos.toByteArray();
- }
- if (data != null && data.length > 0) {
- mSize = data.length;
- mText = getDecodedString(data, mCharset);
- }
- }
-
- /**
- * Load image file of an image part and parse the dimensions and type
- */
- private void loadImage() {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- final Uri uri = getDataUri();
- // We have to get the width and height of the image -- they're needed when adding
- // an attachment in bugle.
- InputStream is = null;
- try {
- is = resolver.openInputStream(uri);
- final BitmapFactory.Options opt = new BitmapFactory.Options();
- opt.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(is, null, opt);
- mContentType = opt.outMimeType;
- mWidth = opt.outWidth;
- mHeight = opt.outHeight;
- if (TextUtils.isEmpty(mContentType)) {
- // BitmapFactory couldn't figure out the image type. That's got to be a bad
- // sign, but see if we can figure it out from the file extension.
- mContentType = extractContentType(context, uri);
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "DatabaseMessages.MmsPart.loadImage: file not found", e);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- Log.e(TAG, "IOException caught while closing stream", e);
- }
- }
- }
- }
-
- /**
- * Load video file of a video part and parse the dimensions and type
- */
- private void loadVideo() {
- // This is a coarse check, and should not be applied to outgoing messages. However,
- // currently, this does not cause any problems.
- if (!VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()) {
- return;
- }
- final Uri uri = getDataUri();
- final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
- try {
- retriever.setDataSource(uri);
- // FLAG: This inadvertently fixes a problem with phone receiving audio
- // messages on some carrier. We should handle this in a less accidental way so that
- // we don't break it again. (The carrier changes the content type in the wrapper
- // in-transit from audio/mp4 to video/3gpp without changing the data)
- // Also note: There is a bug in some OEM device where mmr returns
- // video/ffmpeg for image files. That shouldn't happen here but be aware.
- mContentType =
- retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE);
- final Bitmap bitmap = retriever.getFrameAtTime(-1);
- if (bitmap != null) {
- mWidth = bitmap.getWidth();
- mHeight = bitmap.getHeight();
- } else {
- // Get here if it's not actually video (see above)
- LogUtil.i(LogUtil.BUGLE_TAG, "loadVideo: Got null bitmap from " + uri);
- }
- } catch (IOException e) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting metadata from " + uri, e);
- } finally {
- retriever.release();
- }
- }
-
- /**
- * Get media file size
- */
- private long getMediaFileSize() {
- final Context context = Factory.get().getApplicationContext();
- final Uri uri = getDataUri();
- AssetFileDescriptor fd = null;
- try {
- fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
- if (fd != null) {
- return fd.getParcelFileDescriptor().getStatSize();
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "DatabaseMessages.MmsPart: cound not find media file: " + e, e);
- } finally {
- if (fd != null) {
- try {
- fd.close();
- } catch (final IOException e) {
- LogUtil.e(TAG, "DatabaseMessages.MmsPart: failed to close " + e, e);
- }
- }
- }
- return 0L;
- }
-
- /**
- * @return If the type is a text type that stores text embedded (i.e. in db table)
- */
- private boolean isEmbeddedTextType() {
- return ContentType.TEXT_PLAIN.equals(mContentType)
- || ContentType.APP_SMIL.equals(mContentType)
- || ContentType.TEXT_HTML.equals(mContentType);
- }
-
- /**
- * Get an instance of the MMS part from the part table cursor
- *
- * @param cursor
- * @param loadMedia Whether to load the media file of the part
- * @return
- */
- public static MmsPart get(final Cursor cursor, final boolean loadMedia) {
- final MmsPart part = new MmsPart();
- part.load(cursor, loadMedia);
- return part;
- }
-
- public boolean isText() {
- return ContentType.TEXT_PLAIN.equals(mContentType)
- || ContentType.TEXT_HTML.equals(mContentType)
- || ContentType.APP_WAP_XHTML.equals(mContentType);
- }
-
- public boolean isMedia() {
- return ContentType.isImageType(mContentType)
- || ContentType.isVideoType(mContentType)
- || ContentType.isAudioType(mContentType)
- || ContentType.isVCardType(mContentType);
- }
-
- public boolean isImage() {
- return ContentType.isImageType(mContentType);
- }
-
- public Uri getDataUri() {
- return Uri.parse("content://mms/part/" + mRowId);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- private MmsPart(final Parcel in) {
- mUri = in.readString();
- mRowId = in.readLong();
- mMessageId = in.readLong();
- mContentType = in.readString();
- mText = in.readString();
- mCharset = in.readInt();
- mWidth = in.readInt();
- mHeight = in.readInt();
- mSize = in.readLong();
- }
-
- public static final Parcelable.Creator<MmsPart> CREATOR
- = new Parcelable.Creator<MmsPart>() {
- @Override
- public MmsPart createFromParcel(final Parcel in) {
- return new MmsPart(in);
- }
-
- @Override
- public MmsPart[] newArray(final int size) {
- return new MmsPart[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- out.writeString(mUri);
- out.writeLong(mRowId);
- out.writeLong(mMessageId);
- out.writeString(mContentType);
- out.writeString(mText);
- out.writeInt(mCharset);
- out.writeInt(mWidth);
- out.writeInt(mHeight);
- out.writeLong(mSize);
- }
- }
-
- /**
- * This class provides the same DatabaseMessage interface over a local SMS db message
- */
- public static class LocalDatabaseMessage extends DatabaseMessage implements Parcelable {
- private final int mProtocol;
- private final String mUri;
- private final long mTimestamp;
- private final long mLocalId;
- private final String mConversationId;
-
- public LocalDatabaseMessage(final long localId, final int protocol, final String uri,
- final long timestamp, final String conversationId) {
- mLocalId = localId;
- mProtocol = protocol;
- mUri = uri;
- mTimestamp = timestamp;
- mConversationId = conversationId;
- }
-
- @Override
- public int getProtocol() {
- return mProtocol;
- }
-
- @Override
- public long getTimestampInMillis() {
- return mTimestamp;
- }
-
- @Override
- public String getUri() {
- return mUri;
- }
-
- public long getLocalId() {
- return mLocalId;
- }
-
- public String getConversationId() {
- return mConversationId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- private LocalDatabaseMessage(final Parcel in) {
- mUri = in.readString();
- mConversationId = in.readString();
- mLocalId = in.readLong();
- mTimestamp = in.readLong();
- mProtocol = in.readInt();
- }
-
- public static final Parcelable.Creator<LocalDatabaseMessage> CREATOR
- = new Parcelable.Creator<LocalDatabaseMessage>() {
- @Override
- public LocalDatabaseMessage createFromParcel(final Parcel in) {
- return new LocalDatabaseMessage(in);
- }
-
- @Override
- public LocalDatabaseMessage[] newArray(final int size) {
- return new LocalDatabaseMessage[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- out.writeString(mUri);
- out.writeString(mConversationId);
- out.writeLong(mLocalId);
- out.writeLong(mTimestamp);
- out.writeInt(mProtocol);
- }
- }
-
- /**
- * Address for MMS message
- */
- public static class MmsAddr {
- public static final String[] PROJECTION = new String[] {
- Mms.Addr.ADDRESS,
- Mms.Addr.CHARSET,
- };
- private static int sIota = 0;
- public static final int INDEX_ADDRESS = sIota++;
- public static final int INDEX_CHARSET = sIota++;
-
- public static String get(final Cursor cursor) {
- final int charset = cursor.getInt(INDEX_CHARSET);
- // PduPersister stores the addresses using ISO_8859_1
- // Let's load it using that encoding and convert it back to its original
- // See PduPersister.persistAddress
- return getDecodedString(
- getStringBytes(cursor.getString(INDEX_ADDRESS), CharacterSets.ISO_8859_1),
- charset);
- }
- }
-
- /**
- * Decoded string by character set
- */
- public static String getDecodedString(final byte[] data, final int charset) {
- if (CharacterSets.ANY_CHARSET == charset) {
- return new String(data); // system default encoding.
- } else {
- try {
- final String name = CharacterSets.getMimeName(charset);
- return new String(data, name);
- } catch (final UnsupportedEncodingException e) {
- try {
- return new String(data, CharacterSets.MIMENAME_ISO_8859_1);
- } catch (final UnsupportedEncodingException exception) {
- return new String(data); // system default encoding.
- }
- }
- }
- }
-
- /**
- * Unpack a given String into a byte[].
- */
- public static byte[] getStringBytes(final String data, final int charset) {
- if (CharacterSets.ANY_CHARSET == charset) {
- return data.getBytes();
- } else {
- try {
- final String name = CharacterSets.getMimeName(charset);
- return data.getBytes(name);
- } catch (final UnsupportedEncodingException e) {
- return data.getBytes();
- }
- }
- }
-}
diff --git a/src/com/android/messaging/sms/MmsConfig.java b/src/com/android/messaging/sms/MmsConfig.java
deleted file mode 100755
index f13d785..0000000
--- a/src/com/android/messaging/sms/MmsConfig.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.os.Bundle;
-import android.support.v7.mms.CarrierConfigValuesLoader;
-import android.telephony.SubscriptionInfo;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-import com.google.common.collect.Maps;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * MMS configuration.
- *
- * This is now a wrapper around the BugleCarrierConfigValuesLoader, which does
- * the actual loading and stores the values in a Bundle. This class provides getter
- * methods for values used in the app, which is easier to use than the raw loader
- * class.
- */
-public class MmsConfig {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final int DEFAULT_MAX_TEXT_LENGTH = 2000;
-
- /*
- * Key types
- */
- public static final String KEY_TYPE_INT = "int";
- public static final String KEY_TYPE_BOOL = "bool";
- public static final String KEY_TYPE_STRING = "string";
-
- private static final Map<String, String> sKeyTypeMap = Maps.newHashMap();
- static {
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_MMS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MMS_READ_REPORTS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MMS_DELIVERY_REPORTS,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_HTTP_SOCKET_TIMEOUT, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
- KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_UA_PROF_TAG_NAME, KEY_TYPE_STRING);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_HTTP_PARAMS, KEY_TYPE_STRING);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER, KEY_TYPE_STRING);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_NAI_SUFFIX, KEY_TYPE_STRING);
- }
-
- // A map that stores all MmsConfigs, one per active subscription. For pre-LMSim, this will
- // contain just one entry with the default self sub id; for LMSim and above, this will contain
- // all active sub ids but the default subscription id - the default subscription id will be
- // resolved to an active sub id during runtime.
- private static final Map<Integer, MmsConfig> sSubIdToMmsConfigMap = Maps.newHashMap();
- // The fallback values
- private static final MmsConfig sFallback =
- new MmsConfig(ParticipantData.DEFAULT_SELF_SUB_ID, new Bundle());
-
- // Per-subscription configuration values.
- private final Bundle mValues;
- private final int mSubId;
-
- /**
- * Retrieves the MmsConfig instance associated with the given {@code subId}
- */
- public static MmsConfig get(final int subId) {
- final int realSubId = PhoneUtils.getDefault().getEffectiveSubId(subId);
- synchronized (sSubIdToMmsConfigMap) {
- final MmsConfig mmsConfig = sSubIdToMmsConfigMap.get(realSubId);
- if (mmsConfig == null) {
- // The subId is no longer valid. Fall back to the default config.
- LogUtil.e(LogUtil.BUGLE_TAG, "Get mms config failed: invalid subId. subId=" + subId
- + ", real subId=" + realSubId
- + ", map=" + sSubIdToMmsConfigMap.keySet());
- return sFallback;
- }
- return mmsConfig;
- }
- }
-
- private MmsConfig(final int subId, final Bundle values) {
- mSubId = subId;
- mValues = values;
- }
-
- /**
- * Same as load() but doing it using an async thread from SafeAsyncTask thread pool.
- */
- public static void loadAsync() {
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- load();
- }
- });
- }
-
- /**
- * Reload the device and per-subscription settings.
- */
- public static synchronized void load() {
- final BugleCarrierConfigValuesLoader loader = Factory.get().getCarrierConfigValuesLoader();
- // Rebuild the entire MmsConfig map.
- sSubIdToMmsConfigMap.clear();
- loader.reset();
- if (OsUtil.isAtLeastL_MR1()) {
- final List<SubscriptionInfo> subInfoRecords =
- PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList();
- if (subInfoRecords == null) {
- LogUtil.w(TAG, "Loading mms config failed: no active SIM");
- return;
- }
- for (SubscriptionInfo subInfoRecord : subInfoRecords) {
- final int subId = subInfoRecord.getSubscriptionId();
- final Bundle values = loader.get(subId);
- addMmsConfig(new MmsConfig(subId, values));
- }
- } else {
- final Bundle values = loader.get(ParticipantData.DEFAULT_SELF_SUB_ID);
- addMmsConfig(new MmsConfig(ParticipantData.DEFAULT_SELF_SUB_ID, values));
- }
- }
-
- private static void addMmsConfig(MmsConfig mmsConfig) {
- Assert.isTrue(OsUtil.isAtLeastL_MR1() !=
- (mmsConfig.mSubId == ParticipantData.DEFAULT_SELF_SUB_ID));
- sSubIdToMmsConfigMap.put(mmsConfig.mSubId, mmsConfig);
- }
-
- public int getSmsToMmsTextThreshold() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
- CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD_DEFAULT);
- }
-
- public int getSmsToMmsTextLengthThreshold() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
- CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_DEFAULT);
- }
-
- public int getMaxMessageSize() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE,
- CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE_DEFAULT);
- }
-
- /**
- * Return the largest MaxMessageSize for any subid
- */
- public static int getMaxMaxMessageSize() {
- int maxMax = 0;
- for (MmsConfig config : sSubIdToMmsConfigMap.values()) {
- maxMax = Math.max(maxMax, config.getMaxMessageSize());
- }
- return maxMax > 0 ? maxMax : sFallback.getMaxMessageSize();
- }
-
- public boolean getTransIdEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID,
- CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID_DEFAULT);
- }
-
- public String getEmailGateway() {
- return mValues.getString(CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER,
- CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER_DEFAULT);
- }
-
- public int getMaxImageHeight() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT,
- CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT_DEFAULT);
- }
-
- public int getMaxImageWidth() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH,
- CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH_DEFAULT);
- }
-
- public int getRecipientLimit() {
- final int limit = mValues.getInt(CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT,
- CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT_DEFAULT);
- return limit < 0 ? Integer.MAX_VALUE : limit;
- }
-
- public int getMaxTextLimit() {
- final int max = mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE,
- CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE_DEFAULT);
- return max > -1 ? max : DEFAULT_MAX_TEXT_LENGTH;
- }
-
- public boolean getMultipartSmsEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS,
- CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS_DEFAULT);
- }
-
- public boolean getSendMultipartSmsAsSeparateMessages() {
- return mValues.getBoolean(
- CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
- CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_DEFAULT);
- }
-
- public boolean getSMSDeliveryReportsEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS,
- CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS_DEFAULT);
- }
-
- public boolean getNotifyWapMMSC() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC,
- CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC_DEFAULT);
- }
-
- public boolean isAliasEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED,
- CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED_DEFAULT);
- }
-
- public int getAliasMinChars() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS,
- CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS_DEFAULT);
- }
-
- public int getAliasMaxChars() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS,
- CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS_DEFAULT);
- }
-
- public boolean getAllowAttachAudio() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO,
- CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO_DEFAULT);
- }
-
- public int getMaxSubjectLength() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH,
- CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH_DEFAULT);
- }
-
- public boolean getGroupMmsEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS,
- CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS_DEFAULT);
- }
-
- public boolean getSupportMmsContentDisposition() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
- CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION_DEFAULT);
- }
-
- public boolean getShowCellBroadcast() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS,
- CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS_DEFAULT);
- }
-
- public Object getValue(final String key) {
- return mValues.get(key);
- }
-
- public Set<String> keySet() {
- return mValues.keySet();
- }
-
- public static String getKeyType(final String key) {
- return sKeyTypeMap.get(key);
- }
-
- public void update(final String type, final String key, final String value) {
- BugleCarrierConfigValuesLoader.update(mValues, type, key, value);
- }
-}
diff --git a/src/com/android/messaging/sms/MmsFailureException.java b/src/com/android/messaging/sms/MmsFailureException.java
deleted file mode 100644
index dd702ee..0000000
--- a/src/com/android/messaging/sms/MmsFailureException.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.Assert;
-
-/**
- * Exception for MMS failures
- */
-public class MmsFailureException extends Exception {
- private static final long serialVersionUID = 1L;
-
- /**
- * Hint of how we should retry in case of failure. Take values defined in MmsUtils.
- */
- public final int retryHint;
-
- /**
- * If set, provides a more detailed reason for the failure.
- */
- public final int rawStatus;
-
- private void checkRetryHint() {
- Assert.isTrue(retryHint == MmsUtils.MMS_REQUEST_AUTO_RETRY
- || retryHint == MmsUtils.MMS_REQUEST_MANUAL_RETRY
- || retryHint == MmsUtils.MMS_REQUEST_NO_RETRY);
- }
- /**
- * Creates a new MmsFailureException.
- *
- * @param retryHint Hint for how to retry
- */
- public MmsFailureException(final int retryHint) {
- super();
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- }
-
- public MmsFailureException(final int retryHint, final int rawStatus) {
- super();
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = rawStatus;
- }
-
- /**
- * Creates a new MmsFailureException with the specified detail message.
- *
- * @param retryHint Hint for how to retry
- * @param message the detail message.
- */
- public MmsFailureException(final int retryHint, String message) {
- super(message);
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- }
-
- /**
- * Creates a new MmsFailureException with the specified cause.
- *
- * @param retryHint Hint for how to retry
- * @param cause the cause.
- */
- public MmsFailureException(final int retryHint, Throwable cause) {
- super(cause);
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- }
-
- /**
- * Creates a new MmsFailureException
- * with the specified detail message and cause.
- *
- * @param retryHint Hint for how to retry
- * @param message the detail message.
- * @param cause the cause.
- */
- public MmsFailureException(final int retryHint, String message, Throwable cause) {
- super(message, cause);
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- }
-}
diff --git a/src/com/android/messaging/sms/MmsSender.java b/src/com/android/messaging/sms/MmsSender.java
deleted file mode 100644
index 6dfa81a..0000000
--- a/src/com/android/messaging/sms/MmsSender.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v7.mms.MmsManager;
-import android.telephony.SmsManager;
-
-import com.android.messaging.datamodel.MmsFileProvider;
-import com.android.messaging.datamodel.action.SendMessageAction;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-import com.android.messaging.mmslib.pdu.AcknowledgeInd;
-import com.android.messaging.mmslib.pdu.EncodedStringValue;
-import com.android.messaging.mmslib.pdu.GenericPdu;
-import com.android.messaging.mmslib.pdu.NotifyRespInd;
-import com.android.messaging.mmslib.pdu.PduComposer;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.mmslib.pdu.PduParser;
-import com.android.messaging.mmslib.pdu.RetrieveConf;
-import com.android.messaging.mmslib.pdu.SendConf;
-import com.android.messaging.mmslib.pdu.SendReq;
-import com.android.messaging.receiver.SendStatusReceiver;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-/**
- * Class that sends chat message via MMS.
- *
- * The interface emulates a blocking send similar to making an HTTP request.
- */
-public class MmsSender {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- /**
- * Send an MMS message.
- *
- * @param context Context
- * @param messageUri The unique URI of the message for identifying it during sending
- * @param sendReq The SendReq PDU of the message
- * @throws MmsFailureException
- */
- public static void sendMms(final Context context, final int subId, final Uri messageUri,
- final SendReq sendReq, final Bundle sentIntentExras) throws MmsFailureException {
- sendMms(context,
- subId,
- messageUri,
- null /* locationUrl */,
- sendReq,
- true /* responseImportant */,
- sentIntentExras);
- }
-
- /**
- * Send NotifyRespInd (response to mms auto download).
- *
- * @param context Context
- * @param subId subscription to use to send the response
- * @param transactionId The transaction id of the MMS message
- * @param contentLocation The url of the MMS message
- * @param status The status to send with the NotifyRespInd
- * @throws MmsFailureException
- * @throws InvalidHeaderValueException
- */
- public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
- final byte[] transactionId, final String contentLocation, final int status)
- throws MmsFailureException, InvalidHeaderValueException {
- // Create the M-NotifyResp.ind
- final NotifyRespInd notifyRespInd = new NotifyRespInd(
- PduHeaders.CURRENT_MMS_VERSION, transactionId, status);
- final Uri messageUri = Uri.parse(contentLocation);
- // Pack M-NotifyResp.ind and send it
- sendMms(context,
- subId,
- messageUri,
- MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
- notifyRespInd,
- false /* responseImportant */,
- null /* sentIntentExtras */);
- }
-
- /**
- * Send AcknowledgeInd (response to mms manual download). Ignore failures.
- *
- * @param context Context
- * @param subId The SIM's subId we are currently using
- * @param transactionId The transaction id of the MMS message
- * @param contentLocation The url of the MMS message
- * @throws MmsFailureException
- * @throws InvalidHeaderValueException
- */
- public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
- final byte[] transactionId, final String contentLocation)
- throws MmsFailureException, InvalidHeaderValueException {
- final String selfNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
- // Create the M-Acknowledge.ind
- final AcknowledgeInd acknowledgeInd = new AcknowledgeInd(PduHeaders.CURRENT_MMS_VERSION,
- transactionId);
- acknowledgeInd.setFrom(new EncodedStringValue(selfNumber));
- final Uri messageUri = Uri.parse(contentLocation);
- // Sending
- sendMms(context,
- subId,
- messageUri,
- MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
- acknowledgeInd,
- false /*responseImportant*/,
- null /* sentIntentExtras */);
- }
-
- /**
- * Send a generic PDU.
- *
- * @param context Context
- * @param messageUri The unique URI of the message for identifying it during sending
- * @param locationUrl The optional URL to send to
- * @param pdu The PDU to send
- * @param responseImportant If the sending response is important. Responses to the
- * Sending of AcknowledgeInd and NotifyRespInd are not important.
- * @throws MmsFailureException
- */
- private static void sendMms(final Context context, final int subId, final Uri messageUri,
- final String locationUrl, final GenericPdu pdu, final boolean responseImportant,
- final Bundle sentIntentExtras) throws MmsFailureException {
- // Write PDU to temporary file to send to platform
- final Uri contentUri = writePduToTempFile(context, pdu, subId);
-
- // Construct PendingIntent that will notify us when message sending is complete
- final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION,
- messageUri,
- context,
- SendStatusReceiver.class);
- sentIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
- sentIntent.putExtra(SendMessageAction.EXTRA_RESPONSE_IMPORTANT, responseImportant);
- if (sentIntentExtras != null) {
- sentIntent.putExtras(sentIntentExtras);
- }
- final PendingIntent sentPendingIntent = PendingIntent.getBroadcast(
- context,
- 0 /*request code*/,
- sentIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- // Send the message
- MmsManager.sendMultimediaMessage(subId, context, contentUri, locationUrl,
- sentPendingIntent);
- }
-
- private static Uri writePduToTempFile(final Context context, final GenericPdu pdu, int subId)
- throws MmsFailureException {
- final Uri contentUri = MmsFileProvider.buildRawMmsUri();
- final File tempFile = MmsFileProvider.getFile(contentUri);
- FileOutputStream writer = null;
- try {
- // Ensure rawmms directory exists
- tempFile.getParentFile().mkdirs();
- writer = new FileOutputStream(tempFile);
- final byte[] pduBytes = new PduComposer(context, pdu).make();
- if (pduBytes == null) {
- throw new MmsFailureException(
- MmsUtils.MMS_REQUEST_NO_RETRY, "Failed to compose PDU");
- }
- if (pduBytes.length > MmsConfig.get(subId).getMaxMessageSize()) {
- throw new MmsFailureException(
- MmsUtils.MMS_REQUEST_NO_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
- }
- writer.write(pduBytes);
- } catch (final IOException e) {
- if (tempFile != null) {
- tempFile.delete();
- }
- LogUtil.e(TAG, "Cannot create temporary file " + tempFile.getAbsolutePath(), e);
- throw new MmsFailureException(
- MmsUtils.MMS_REQUEST_AUTO_RETRY, "Cannot create raw mms file");
- } catch (final OutOfMemoryError e) {
- if (tempFile != null) {
- tempFile.delete();
- }
- LogUtil.e(TAG, "Out of memory in composing PDU", e);
- throw new MmsFailureException(
- MmsUtils.MMS_REQUEST_MANUAL_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
- } finally {
- if (writer != null) {
- try {
- writer.close();
- } catch (final IOException e) {
- // no action we can take here
- }
- }
- }
- return contentUri;
- }
-
- public static SendConf parseSendConf(byte[] response, int subId) {
- if (response != null) {
- final GenericPdu respPdu = new PduParser(
- response, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
- if (respPdu != null) {
- if (respPdu instanceof SendConf) {
- return (SendConf) respPdu;
- } else {
- LogUtil.e(TAG, "MmsSender: send response not SendConf");
- }
- } else {
- // Invalid PDU
- LogUtil.e(TAG, "MmsSender: send invalid response");
- }
- }
- // Empty or invalid response
- return null;
- }
-
- /**
- * Download an MMS message.
- *
- * @param context Context
- * @param contentLocation The url of the MMS message
- * @throws MmsFailureException
- * @throws InvalidHeaderValueException
- */
- public static void downloadMms(final Context context, final int subId,
- final String contentLocation, Bundle extras) throws MmsFailureException,
- InvalidHeaderValueException {
- final Uri requestUri = Uri.parse(contentLocation);
- final Uri contentUri = MmsFileProvider.buildRawMmsUri();
-
- final Intent downloadedIntent = new Intent(SendStatusReceiver.MMS_DOWNLOADED_ACTION,
- requestUri,
- context,
- SendStatusReceiver.class);
- downloadedIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
- if (extras != null) {
- downloadedIntent.putExtras(extras);
- }
- final PendingIntent downloadedPendingIntent = PendingIntent.getBroadcast(
- context,
- 0 /*request code*/,
- downloadedIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- MmsManager.downloadMultimediaMessage(subId, context, contentLocation, contentUri,
- downloadedPendingIntent);
- }
-
- public static RetrieveConf parseRetrieveConf(byte[] data, int subId) {
- if (data != null) {
- final GenericPdu pdu = new PduParser(
- data, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
- if (pdu != null) {
- if (pdu instanceof RetrieveConf) {
- return (RetrieveConf) pdu;
- } else {
- LogUtil.e(TAG, "MmsSender: downloaded pdu not RetrieveConf: "
- + pdu.getClass().getName());
- }
- } else {
- LogUtil.e(TAG, "MmsSender: downloaded pdu could not be parsed (invalid)");
- }
- }
- LogUtil.e(TAG, "MmsSender: downloaded pdu is empty");
- return null;
- }
-
- // Process different result code from platform MMS service
- public static int getErrorResultStatus(int resultCode, int httpStatusCode) {
- Assert.isFalse(resultCode == Activity.RESULT_OK);
- switch (resultCode) {
- case SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS:
- case SmsManager.MMS_ERROR_IO_ERROR:
- return MmsUtils.MMS_REQUEST_AUTO_RETRY;
- case SmsManager.MMS_ERROR_INVALID_APN:
- case SmsManager.MMS_ERROR_CONFIGURATION_ERROR:
- case SmsManager.MMS_ERROR_NO_DATA_NETWORK:
- case SmsManager.MMS_ERROR_UNSPECIFIED:
- return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
- case SmsManager.MMS_ERROR_HTTP_FAILURE:
- if (httpStatusCode == 404) {
- return MmsUtils.MMS_REQUEST_NO_RETRY;
- } else {
- return MmsUtils.MMS_REQUEST_AUTO_RETRY;
- }
- default:
- return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
- }
- }
-}
diff --git a/src/com/android/messaging/sms/MmsSmsUtils.java b/src/com/android/messaging/sms/MmsSmsUtils.java
deleted file mode 100644
index 1a0ef99..0000000
--- a/src/com/android/messaging/sms/MmsSmsUtils.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.BaseColumns;
-import android.text.TextUtils;
-import android.util.Patterns;
-
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.util.LogUtil;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Utility functions for the Messaging Service
- */
-public class MmsSmsUtils {
- private MmsSmsUtils() {
- // Forbidden being instantiated.
- }
-
- // An alias (or commonly called "nickname") is:
- // Nickname must begin with a letter.
- // Only letters a-z, numbers 0-9, or . are allowed in Nickname field.
- public static boolean isAlias(final String string, final int subId) {
- if (!MmsConfig.get(subId).isAliasEnabled()) {
- return false;
- }
-
- final int len = string == null ? 0 : string.length();
-
- if (len < MmsConfig.get(subId).getAliasMinChars() ||
- len > MmsConfig.get(subId).getAliasMaxChars()) {
- return false;
- }
-
- if (!Character.isLetter(string.charAt(0))) { // Nickname begins with a letter
- return false;
- }
- for (int i = 1; i < len; i++) {
- final char c = string.charAt(i);
- if (!(Character.isLetterOrDigit(c) || c == '.')) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * mailbox = name-addr
- * name-addr = [display-name] angle-addr
- * angle-addr = [CFWS] "<" addr-spec ">" [CFWS]
- */
- public static final Pattern NAME_ADDR_EMAIL_PATTERN =
- Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
-
- public static String extractAddrSpec(final String address) {
- final Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
-
- if (match.matches()) {
- return match.group(2);
- }
- return address;
- }
-
- /**
- * Returns true if the address is an email address
- *
- * @param address the input address to be tested
- * @return true if address is an email address
- */
- public static boolean isEmailAddress(final String address) {
- if (TextUtils.isEmpty(address)) {
- return false;
- }
-
- final String s = extractAddrSpec(address);
- final Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
- return match.matches();
- }
-
- /**
- * Returns true if the number is a Phone number
- *
- * @param number the input number to be tested
- * @return true if number is a Phone number
- */
- public static boolean isPhoneNumber(final String number) {
- if (TextUtils.isEmpty(number)) {
- return false;
- }
-
- final Matcher match = Patterns.PHONE.matcher(number);
- return match.matches();
- }
-
- /**
- * Check if MMS is required when sending to email address
- *
- * @param destinationHasEmailAddress destination includes an email address
- * @return true if MMS is required.
- */
- public static boolean getRequireMmsForEmailAddress(final boolean destinationHasEmailAddress,
- final int subId) {
- if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway())) {
- return false;
- } else {
- return destinationHasEmailAddress;
- }
- }
-
- /**
- * Helper functions for the "threads" table used by MMS and SMS.
- */
- public static final class Threads implements android.provider.Telephony.ThreadsColumns {
- private static final String[] ID_PROJECTION = { BaseColumns._ID };
- private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
- "content://mms-sms/threadID");
- public static final Uri CONTENT_URI = Uri.withAppendedPath(
- android.provider.Telephony.MmsSms.CONTENT_URI, "conversations");
-
- // No one should construct an instance of this class.
- private Threads() {
- }
-
- /**
- * This is a single-recipient version of
- * getOrCreateThreadId. It's convenient for use with SMS
- * messages.
- */
- public static long getOrCreateThreadId(final Context context, final String recipient) {
- final Set<String> recipients = new HashSet<String>();
-
- recipients.add(recipient);
- return getOrCreateThreadId(context, recipients);
- }
-
- /**
- * Given the recipients list and subject of an unsaved message,
- * return its thread ID. If the message starts a new thread,
- * allocate a new thread ID. Otherwise, use the appropriate
- * existing thread ID.
- *
- * Find the thread ID of the same set of recipients (in
- * any order, without any additions). If one
- * is found, return it. Otherwise, return a unique thread ID.
- */
- public static long getOrCreateThreadId(
- final Context context, final Set<String> recipients) {
- final Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
-
- for (String recipient : recipients) {
- if (isEmailAddress(recipient)) {
- recipient = extractAddrSpec(recipient);
- }
-
- uriBuilder.appendQueryParameter("recipient", recipient);
- }
-
- final Uri uri = uriBuilder.build();
- //if (DEBUG) Rlog.v(TAG, "getOrCreateThreadId uri: " + uri);
-
- final Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
- uri, ID_PROJECTION, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- return cursor.getLong(0);
- } else {
- LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG,
- "getOrCreateThreadId returned no rows!");
- }
- } finally {
- cursor.close();
- }
- }
-
- LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "getOrCreateThreadId failed with "
- + LogUtil.sanitizePII(recipients.toString()));
- throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
- }
- }
-}
diff --git a/src/com/android/messaging/sms/MmsUtils.java b/src/com/android/messaging/sms/MmsUtils.java
deleted file mode 100644
index 913e9a6..0000000
--- a/src/com/android/messaging/sms/MmsUtils.java
+++ /dev/null
@@ -1,2747 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.media.MediaMetadataRetriever;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.provider.Telephony;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.provider.Telephony.Threads;
-import android.telephony.SmsManager;
-import android.telephony.SmsMessage;
-import android.text.TextUtils;
-import android.text.util.Rfc822Token;
-import android.text.util.Rfc822Tokenizer;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.action.DownloadMmsAction;
-import com.android.messaging.datamodel.action.SendMessageAction;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-import com.android.messaging.mmslib.MmsException;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.mmslib.pdu.CharacterSets;
-import com.android.messaging.mmslib.pdu.EncodedStringValue;
-import com.android.messaging.mmslib.pdu.GenericPdu;
-import com.android.messaging.mmslib.pdu.NotificationInd;
-import com.android.messaging.mmslib.pdu.PduBody;
-import com.android.messaging.mmslib.pdu.PduComposer;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.mmslib.pdu.PduParser;
-import com.android.messaging.mmslib.pdu.PduPart;
-import com.android.messaging.mmslib.pdu.PduPersister;
-import com.android.messaging.mmslib.pdu.RetrieveConf;
-import com.android.messaging.mmslib.pdu.SendConf;
-import com.android.messaging.mmslib.pdu.SendReq;
-import com.android.messaging.sms.SmsSender.SendResult;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.EmailAddress;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.ImageUtils.ImageResizer;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaMetadataRetrieverWrapper;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.base.Joiner;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * Utils for sending sms/mms messages.
- */
-public class MmsUtils {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- public static final boolean DEFAULT_DELIVERY_REPORT_MODE = false;
- public static final boolean DEFAULT_READ_REPORT_MODE = false;
- public static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 7 * 24 * 60 * 60;
- public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
-
- public static final int MAX_SMS_RETRY = 3;
-
- /**
- * MMS request succeeded
- */
- public static final int MMS_REQUEST_SUCCEEDED = 0;
- /**
- * MMS request failed with a transient error and can be retried automatically
- */
- public static final int MMS_REQUEST_AUTO_RETRY = 1;
- /**
- * MMS request failed with an error and can be retried manually
- */
- public static final int MMS_REQUEST_MANUAL_RETRY = 2;
- /**
- * MMS request failed with a specific error and should not be retried
- */
- public static final int MMS_REQUEST_NO_RETRY = 3;
-
- public static final String getRequestStatusDescription(final int status) {
- switch (status) {
- case MMS_REQUEST_SUCCEEDED:
- return "SUCCEEDED";
- case MMS_REQUEST_AUTO_RETRY:
- return "AUTO_RETRY";
- case MMS_REQUEST_MANUAL_RETRY:
- return "MANUAL_RETRY";
- case MMS_REQUEST_NO_RETRY:
- return "NO_RETRY";
- default:
- return String.valueOf(status) + " (check MmsUtils)";
- }
- }
-
- public static final int PDU_HEADER_VALUE_UNDEFINED = 0;
-
- private static final int DEFAULT_DURATION = 5000; //ms
-
- // amount of space to leave in a MMS for text and overhead.
- private static final int MMS_MAX_SIZE_SLOP = 1024;
- public static final long INVALID_TIMESTAMP = 0L;
- private static String[] sNoSubjectStrings;
-
- public static class MmsInfo {
- public Uri mUri;
- public int mMessageSize;
- public PduBody mPduBody;
- }
-
- // Sync all remote messages apart from drafts
- private static final String REMOTE_SMS_SELECTION = String.format(
- Locale.US,
- "(%s IN (%d, %d, %d, %d, %d))",
- Sms.TYPE,
- Sms.MESSAGE_TYPE_INBOX,
- Sms.MESSAGE_TYPE_OUTBOX,
- Sms.MESSAGE_TYPE_QUEUED,
- Sms.MESSAGE_TYPE_FAILED,
- Sms.MESSAGE_TYPE_SENT);
-
- private static final String REMOTE_MMS_SELECTION = String.format(
- Locale.US,
- "((%s IN (%d, %d, %d, %d)) AND (%s IN (%d, %d, %d)))",
- Mms.MESSAGE_BOX,
- Mms.MESSAGE_BOX_INBOX,
- Mms.MESSAGE_BOX_OUTBOX,
- Mms.MESSAGE_BOX_SENT,
- Mms.MESSAGE_BOX_FAILED,
- Mms.MESSAGE_TYPE,
- PduHeaders.MESSAGE_TYPE_SEND_REQ,
- PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND,
- PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
-
- /**
- * Type selection for importing sms messages.
- *
- * @return The SQL selection for importing sms messages
- */
- public static String getSmsTypeSelectionSql() {
- return REMOTE_SMS_SELECTION;
- }
-
- /**
- * Type selection for importing mms messages.
- *
- * @return The SQL selection for importing mms messages. This selects the message type,
- * not including the selection on timestamp.
- */
- public static String getMmsTypeSelectionSql() {
- return REMOTE_MMS_SELECTION;
- }
-
- // SMIL spec: http://www.w3.org/TR/SMIL3
-
- private static final String sSmilImagePart =
- "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
- "<img src=\"%s\" region=\"Image\" />" +
- "</par>";
-
- private static final String sSmilVideoPart =
- "<par dur=\"%2$dms\">" +
- "<video src=\"%1$s\" dur=\"%2$dms\" region=\"Image\" />" +
- "</par>";
-
- private static final String sSmilAudioPart =
- "<par dur=\"%2$dms\">" +
- "<audio src=\"%1$s\" dur=\"%2$dms\" />" +
- "</par>";
-
- private static final String sSmilTextPart =
- "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
- "<text src=\"%s\" region=\"Text\" />" +
- "</par>";
-
- private static final String sSmilPart =
- "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
- "<ref src=\"%s\" />" +
- "</par>";
-
- private static final String sSmilTextOnly =
- "<smil>" +
- "<head>" +
- "<layout>" +
- "<root-layout/>" +
- "<region id=\"Text\" top=\"0\" left=\"0\" "
- + "height=\"100%%\" width=\"100%%\"/>" +
- "</layout>" +
- "</head>" +
- "<body>" +
- "%s" + // constructed body goes here
- "</body>" +
- "</smil>";
-
- private static final String sSmilVisualAttachmentsOnly =
- "<smil>" +
- "<head>" +
- "<layout>" +
- "<root-layout/>" +
- "<region id=\"Image\" fit=\"meet\" top=\"0\" left=\"0\" "
- + "height=\"100%%\" width=\"100%%\"/>" +
- "</layout>" +
- "</head>" +
- "<body>" +
- "%s" + // constructed body goes here
- "</body>" +
- "</smil>";
-
- private static final String sSmilVisualAttachmentsWithText =
- "<smil>" +
- "<head>" +
- "<layout>" +
- "<root-layout/>" +
- "<region id=\"Image\" fit=\"meet\" top=\"0\" left=\"0\" "
- + "height=\"80%%\" width=\"100%%\"/>" +
- "<region id=\"Text\" top=\"80%%\" left=\"0\" height=\"20%%\" "
- + "width=\"100%%\"/>" +
- "</layout>" +
- "</head>" +
- "<body>" +
- "%s" + // constructed body goes here
- "</body>" +
- "</smil>";
-
- private static final String sSmilNonVisualAttachmentsOnly =
- "<smil>" +
- "<head>" +
- "<layout>" +
- "<root-layout/>" +
- "</layout>" +
- "</head>" +
- "<body>" +
- "%s" + // constructed body goes here
- "</body>" +
- "</smil>";
-
- private static final String sSmilNonVisualAttachmentsWithText = sSmilTextOnly;
-
- public static final String MMS_DUMP_PREFIX = "mmsdump-";
- public static final String SMS_DUMP_PREFIX = "smsdump-";
-
- public static final int MIN_VIDEO_BYTES_PER_SECOND = 4 * 1024;
- public static final int MIN_IMAGE_BYTE_SIZE = 16 * 1024;
- public static final int MAX_VIDEO_ATTACHMENT_COUNT = 1;
-
- public static MmsInfo makePduBody(final Context context, final MessageData message,
- final int subId) {
- final PduBody pb = new PduBody();
-
- // Compute data size requirements for this message: count up images and total size of
- // non-image attachments.
- int totalLength = 0;
- int countImage = 0;
- for (final MessagePartData part : message.getParts()) {
- if (part.isAttachment()) {
- final String contentType = part.getContentType();
- if (ContentType.isImageType(contentType)) {
- countImage++;
- } else if (ContentType.isVCardType(contentType)) {
- totalLength += getDataLength(context, part.getContentUri());
- } else {
- totalLength += getMediaFileSize(part.getContentUri());
- }
- }
- }
- final long minSize = countImage * MIN_IMAGE_BYTE_SIZE;
- final int byteBudget = MmsConfig.get(subId).getMaxMessageSize() - totalLength
- - MMS_MAX_SIZE_SLOP;
- final double budgetFactor =
- minSize > 0 ? Math.max(1.0, byteBudget / ((double) minSize)) : 1;
- final int bytesPerImage = (int) (budgetFactor * MIN_IMAGE_BYTE_SIZE);
- final int widthLimit = MmsConfig.get(subId).getMaxImageWidth();
- final int heightLimit = MmsConfig.get(subId).getMaxImageHeight();
-
- // Actually add the attachments, shrinking images appropriately.
- int index = 0;
- totalLength = 0;
- boolean hasVisualAttachment = false;
- boolean hasNonVisualAttachment = false;
- boolean hasText = false;
- final StringBuilder smilBody = new StringBuilder();
- for (final MessagePartData part : message.getParts()) {
- String srcName;
- if (part.isAttachment()) {
- String contentType = part.getContentType();
- if (ContentType.isImageType(contentType)) {
- // There's a good chance that if we selected the image from our media picker the
- // content type is image/*. Fix the content type here for gifs so that we only
- // need to open the input stream once. All other gif vs static image checks will
- // only have to do a string comparison which is much cheaper.
- final boolean isGif = ImageUtils.isGif(contentType, part.getContentUri());
- contentType = isGif ? ContentType.IMAGE_GIF : contentType;
- srcName = String.format(isGif ? "image%06d.gif" : "image%06d.jpg", index);
- smilBody.append(String.format(sSmilImagePart, srcName));
- totalLength += addPicturePart(context, pb, index, part,
- widthLimit, heightLimit, bytesPerImage, srcName, contentType);
- hasVisualAttachment = true;
- } else if (ContentType.isVideoType(contentType)) {
- srcName = String.format("video%06d.mp4", index);
- final int length = addVideoPart(context, pb, part, srcName);
- totalLength += length;
- smilBody.append(String.format(sSmilVideoPart, srcName,
- getMediaDurationMs(context, part, DEFAULT_DURATION)));
- hasVisualAttachment = true;
- } else if (ContentType.isVCardType(contentType)) {
- srcName = String.format("contact%06d.vcf", index);
- totalLength += addVCardPart(context, pb, part, srcName);
- smilBody.append(String.format(sSmilPart, srcName));
- hasNonVisualAttachment = true;
- } else if (ContentType.isAudioType(contentType)) {
- srcName = String.format("recording%06d.amr", index);
- totalLength += addOtherPart(context, pb, part, srcName);
- final int duration = getMediaDurationMs(context, part, -1);
- Assert.isTrue(duration != -1);
- smilBody.append(String.format(sSmilAudioPart, srcName, duration));
- hasNonVisualAttachment = true;
- } else {
- srcName = String.format("other%06d.dat", index);
- totalLength += addOtherPart(context, pb, part, srcName);
- smilBody.append(String.format(sSmilPart, srcName));
- }
- index++;
- }
- if (!TextUtils.isEmpty(part.getText())) {
- hasText = true;
- }
- }
-
- if (hasText) {
- final String srcName = String.format("text.%06d.txt", index);
- final String text = message.getMessageText();
- totalLength += addTextPart(context, pb, text, srcName);
-
- // Append appropriate SMIL to the body.
- smilBody.append(String.format(sSmilTextPart, srcName));
- }
-
- final String smilTemplate = getSmilTemplate(hasVisualAttachment,
- hasNonVisualAttachment, hasText);
- addSmilPart(pb, smilTemplate, smilBody.toString());
-
- final MmsInfo mmsInfo = new MmsInfo();
- mmsInfo.mPduBody = pb;
- mmsInfo.mMessageSize = totalLength;
-
- return mmsInfo;
- }
-
- private static int getMediaDurationMs(final Context context, final MessagePartData part,
- final int defaultDurationMs) {
- Assert.notNull(context);
- Assert.notNull(part);
- Assert.isTrue(ContentType.isAudioType(part.getContentType()) ||
- ContentType.isVideoType(part.getContentType()));
-
- final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
- try {
- retriever.setDataSource(part.getContentUri());
- return retriever.extractInteger(
- MediaMetadataRetriever.METADATA_KEY_DURATION, defaultDurationMs);
- } catch (final IOException e) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting duration from " + part.getContentUri(), e);
- return defaultDurationMs;
- } finally {
- retriever.release();
- }
- }
-
- private static void setPartContentLocationAndId(final PduPart part, final String srcName) {
- // Set Content-Location.
- part.setContentLocation(srcName.getBytes());
-
- // Set Content-Id.
- final int index = srcName.lastIndexOf(".");
- final String contentId = (index == -1) ? srcName : srcName.substring(0, index);
- part.setContentId(contentId.getBytes());
- }
-
- private static int addTextPart(final Context context, final PduBody pb,
- final String text, final String srcName) {
- final PduPart part = new PduPart();
-
- // Set Charset if it's a text media.
- part.setCharset(CharacterSets.UTF_8);
-
- // Set Content-Type.
- part.setContentType(ContentType.TEXT_PLAIN.getBytes());
-
- // Set Content-Location.
- setPartContentLocationAndId(part, srcName);
-
- part.setData(text.getBytes());
-
- pb.addPart(part);
-
- return part.getData().length;
- }
-
- private static int addPicturePart(final Context context, final PduBody pb, final int index,
- final MessagePartData messagePart, int widthLimit, int heightLimit,
- final int maxPartSize, final String srcName, final String contentType) {
- final Uri imageUri = messagePart.getContentUri();
- final int width = messagePart.getWidth();
- final int height = messagePart.getHeight();
-
- // Swap the width and height limits to match the orientation of the image so we scale the
- // picture as little as possible.
- if ((height > width) != (heightLimit > widthLimit)) {
- final int temp = widthLimit;
- widthLimit = heightLimit;
- heightLimit = temp;
- }
-
- final int orientation = ImageUtils.getOrientation(context, imageUri);
- int imageSize = getDataLength(context, imageUri);
- if (imageSize <= 0) {
- LogUtil.e(TAG, "Can't get image", new Exception());
- return 0;
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPicturePart size: " + imageSize + " width: "
- + width + " widthLimit: " + widthLimit
- + " height: " + height
- + " heightLimit: " + heightLimit);
- }
-
- PduPart part;
- // Check if we're already within the limits - in which case we don't need to resize.
- // The size can be zero here, even when the media has content. See the comment in
- // MediaModel.initMediaSize. Sometimes it'll compute zero and it's costly to read the
- // whole stream to compute the size. When we call getResizedImageAsPart(), we'll correctly
- // set the size.
- if (imageSize <= maxPartSize &&
- width <= widthLimit &&
- height <= heightLimit &&
- (orientation == android.media.ExifInterface.ORIENTATION_UNDEFINED ||
- orientation == android.media.ExifInterface.ORIENTATION_NORMAL)) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPicturePart - already sized");
- }
- part = new PduPart();
- part.setDataUri(imageUri);
- part.setContentType(contentType.getBytes());
- } else {
- part = getResizedImageAsPart(widthLimit, heightLimit, maxPartSize,
- width, height, orientation, imageUri, context, contentType);
- if (part == null) {
- final OutOfMemoryError e = new OutOfMemoryError();
- LogUtil.e(TAG, "Can't resize image: not enough memory?", e);
- throw e;
- }
- imageSize = part.getData().length;
- }
-
- setPartContentLocationAndId(part, srcName);
-
- pb.addPart(index, part);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPicturePart size: " + imageSize);
- }
-
- return imageSize;
- }
-
- private static void addPartForUri(final Context context, final PduBody pb,
- final String srcName, final Uri uri, final String contentType) {
- final PduPart part = new PduPart();
- part.setDataUri(uri);
- part.setContentType(contentType.getBytes());
-
- setPartContentLocationAndId(part, srcName);
-
- pb.addPart(part);
- }
-
- private static int addVCardPart(final Context context, final PduBody pb,
- final MessagePartData messagePart, final String srcName) {
- final Uri vcardUri = messagePart.getContentUri();
- final String contentType = messagePart.getContentType();
- final int vcardSize = getDataLength(context, vcardUri);
- if (vcardSize <= 0) {
- LogUtil.e(TAG, "Can't get vcard", new Exception());
- return 0;
- }
-
- addPartForUri(context, pb, srcName, vcardUri, contentType);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addVCardPart size: " + vcardSize);
- }
-
- return vcardSize;
- }
-
- /**
- * Add video part recompressing video if necessary. If recompression fails, part is not
- * added.
- */
- private static int addVideoPart(final Context context, final PduBody pb,
- final MessagePartData messagePart, final String srcName) {
- final Uri attachmentUri = messagePart.getContentUri();
- String contentType = messagePart.getContentType();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPart attachmentUrl: " + attachmentUri.toString());
- }
-
- if (TextUtils.isEmpty(contentType)) {
- contentType = ContentType.VIDEO_3G2;
- }
-
- addPartForUri(context, pb, srcName, attachmentUri, contentType);
- return (int) getMediaFileSize(attachmentUri);
- }
-
- private static int addOtherPart(final Context context, final PduBody pb,
- final MessagePartData messagePart, final String srcName) {
- final Uri attachmentUri = messagePart.getContentUri();
- final String contentType = messagePart.getContentType();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPart attachmentUrl: " + attachmentUri.toString());
- }
-
- final int dataSize = (int) getMediaFileSize(attachmentUri);
-
- addPartForUri(context, pb, srcName, attachmentUri, contentType);
-
- return dataSize;
- }
-
- private static void addSmilPart(final PduBody pb, final String smilTemplate,
- final String smilBody) {
- final PduPart smilPart = new PduPart();
- smilPart.setContentId("smil".getBytes());
- smilPart.setContentLocation("smil.xml".getBytes());
- smilPart.setContentType(ContentType.APP_SMIL.getBytes());
- final String smil = String.format(smilTemplate, smilBody);
- smilPart.setData(smil.getBytes());
- pb.addPart(0, smilPart);
- }
-
- private static String getSmilTemplate(final boolean hasVisualAttachments,
- final boolean hasNonVisualAttachments, final boolean hasText) {
- if (hasVisualAttachments) {
- return hasText ? sSmilVisualAttachmentsWithText : sSmilVisualAttachmentsOnly;
- }
- if (hasNonVisualAttachments) {
- return hasText ? sSmilNonVisualAttachmentsWithText : sSmilNonVisualAttachmentsOnly;
- }
- return sSmilTextOnly;
- }
-
- private static int getDataLength(final Context context, final Uri uri) {
- InputStream is = null;
- try {
- is = context.getContentResolver().openInputStream(uri);
- try {
- return is == null ? 0 : is.available();
- } catch (final IOException e) {
- LogUtil.e(TAG, "getDataLength couldn't stream: " + uri, e);
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "getDataLength couldn't open: " + uri, e);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- LogUtil.e(TAG, "getDataLength couldn't close: " + uri, e);
- }
- }
- }
- return 0;
- }
-
- /**
- * Returns {@code true} if group mms is turned on,
- * {@code false} otherwise.
- *
- * For the group mms feature to be enabled, the following must be true:
- * 1. the feature is enabled in mms_config.xml (currently on by default)
- * 2. the feature is enabled in the SMS settings page
- *
- * @return true if group mms is supported
- */
- public static boolean groupMmsEnabled(final int subId) {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
- final String groupMmsKey = resources.getString(R.string.group_mms_pref_key);
- final boolean groupMmsEnabledDefault = resources.getBoolean(R.bool.group_mms_pref_default);
- final boolean groupMmsPrefOn = prefs.getBoolean(groupMmsKey, groupMmsEnabledDefault);
- return MmsConfig.get(subId).getGroupMmsEnabled() && groupMmsPrefOn;
- }
-
- /**
- * Get a version of this image resized to fit the given dimension and byte-size limits. Note
- * that the content type of the resulting PduPart may not be the same as the content type of
- * this UriImage; always call {@link PduPart#getContentType()} to get the new content type.
- *
- * @param widthLimit The width limit, in pixels
- * @param heightLimit The height limit, in pixels
- * @param byteLimit The binary size limit, in bytes
- * @param width The image width, in pixels
- * @param height The image height, in pixels
- * @param orientation Orientation constant from ExifInterface for rotating or flipping the
- * image
- * @param imageUri Uri to the image data
- * @param context Needed to open the image
- * @return A new PduPart containing the resized image data
- */
- private static PduPart getResizedImageAsPart(final int widthLimit,
- final int heightLimit, final int byteLimit, final int width, final int height,
- final int orientation, final Uri imageUri, final Context context, final String contentType) {
- final PduPart part = new PduPart();
-
- final byte[] data = ImageResizer.getResizedImageData(width, height, orientation,
- widthLimit, heightLimit, byteLimit, imageUri, context, contentType);
- if (data == null) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Resize image failed.");
- }
- return null;
- }
-
- part.setData(data);
- // Any static images will be compressed into a jpeg
- final String contentTypeOfResizedImage = ImageUtils.isGif(contentType, imageUri)
- ? ContentType.IMAGE_GIF : ContentType.IMAGE_JPEG;
- part.setContentType(contentTypeOfResizedImage.getBytes());
-
- return part;
- }
-
- /**
- * Get media file size
- */
- public static long getMediaFileSize(final Uri uri) {
- final Context context = Factory.get().getApplicationContext();
- AssetFileDescriptor fd = null;
- try {
- fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
- if (fd != null) {
- return fd.getParcelFileDescriptor().getStatSize();
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "MmsUtils.getMediaFileSize: cound not find media file: " + e, e);
- } finally {
- if (fd != null) {
- try {
- fd.close();
- } catch (final IOException e) {
- LogUtil.e(TAG, "MmsUtils.getMediaFileSize: failed to close " + e, e);
- }
- }
- }
- return 0L;
- }
-
- // Code for extracting the actual phone numbers for the participants in a conversation,
- // given a thread id.
-
- private static final Uri ALL_THREADS_URI =
- Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
-
- private static final String[] RECIPIENTS_PROJECTION = {
- Threads._ID,
- Threads.RECIPIENT_IDS
- };
-
- private static final int RECIPIENT_IDS = 1;
-
- public static List<String> getRecipientsByThread(final long threadId) {
- final String spaceSepIds = getRawRecipientIdsForThread(threadId);
- if (!TextUtils.isEmpty(spaceSepIds)) {
- final Context context = Factory.get().getApplicationContext();
- return getAddresses(context, spaceSepIds);
- }
- return null;
- }
-
- // NOTE: There are phones on which you can't get the recipients from the thread id for SMS
- // until you have a message in the conversation!
- public static String getRawRecipientIdsForThread(final long threadId) {
- if (threadId <= 0) {
- return null;
- }
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver cr = context.getContentResolver();
- final Cursor thread = cr.query(
- ALL_THREADS_URI,
- RECIPIENTS_PROJECTION, "_id=?", new String[] { String.valueOf(threadId) }, null);
- if (thread != null) {
- try {
- if (thread.moveToFirst()) {
- // recipientIds will be a space-separated list of ids into the
- // canonical addresses table.
- return thread.getString(RECIPIENT_IDS);
- }
- } finally {
- thread.close();
- }
- }
- return null;
- }
-
- private static final Uri SINGLE_CANONICAL_ADDRESS_URI =
- Uri.parse("content://mms-sms/canonical-address");
-
- private static List<String> getAddresses(final Context context, final String spaceSepIds) {
- final List<String> numbers = new ArrayList<String>();
- final String[] ids = spaceSepIds.split(" ");
- for (final String id : ids) {
- long longId;
-
- try {
- longId = Long.parseLong(id);
- if (longId < 0) {
- LogUtil.e(TAG, "MmsUtils.getAddresses: invalid id " + longId);
- continue;
- }
- } catch (final NumberFormatException ex) {
- LogUtil.e(TAG, "MmsUtils.getAddresses: invalid id. " + ex, ex);
- // skip this id
- continue;
- }
-
- // TODO: build a single query where we get all the addresses at once.
- Cursor c = null;
- try {
- c = context.getContentResolver().query(
- ContentUris.withAppendedId(SINGLE_CANONICAL_ADDRESS_URI, longId),
- null, null, null, null);
- } catch (final Exception e) {
- LogUtil.e(TAG, "MmsUtils.getAddresses: query failed for id " + longId, e);
- }
- if (c != null) {
- try {
- if (c.moveToFirst()) {
- final String number = c.getString(0);
- if (!TextUtils.isEmpty(number)) {
- numbers.add(number);
- } else {
- LogUtil.w(TAG, "Canonical MMS/SMS address is empty for id: " + longId);
- }
- }
- } finally {
- c.close();
- }
- }
- }
- if (numbers.isEmpty()) {
- LogUtil.w(TAG, "No MMS addresses found from ids string [" + spaceSepIds + "]");
- }
- return numbers;
- }
-
- // Get telephony SMS thread ID
- public static long getOrCreateSmsThreadId(final Context context, final String dest) {
- // use destinations to determine threadId
- final Set<String> recipients = new HashSet<String>();
- recipients.add(dest);
- try {
- return MmsSmsUtils.Threads.getOrCreateThreadId(context, recipients);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: getting thread id failed: " + e);
- return -1;
- }
- }
-
- // Get telephony SMS thread ID
- public static long getOrCreateThreadId(final Context context, final List<String> dests) {
- if (dests == null || dests.size() == 0) {
- return -1;
- }
- // use destinations to determine threadId
- final Set<String> recipients = new HashSet<String>(dests);
- try {
- return MmsSmsUtils.Threads.getOrCreateThreadId(context, recipients);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: getting thread id failed: " + e);
- return -1;
- }
- }
-
- /**
- * Add an SMS to the given URI with thread_id specified.
- *
- * @param resolver the content resolver to use
- * @param uri the URI to add the message to
- * @param subId subId for the receiving sim
- * @param address the address of the sender
- * @param body the body of the message
- * @param subject the psuedo-subject of the message
- * @param date the timestamp for the message
- * @param read true if the message has been read, false if not
- * @param threadId the thread_id of the message
- * @return the URI for the new message
- */
- private static Uri addMessageToUri(final ContentResolver resolver,
- final Uri uri, final int subId, final String address, final String body,
- final String subject, final Long date, final boolean read, final boolean seen,
- final int status, final int type, final long threadId) {
- final ContentValues values = new ContentValues(7);
-
- values.put(Telephony.Sms.ADDRESS, address);
- if (date != null) {
- values.put(Telephony.Sms.DATE, date);
- }
- values.put(Telephony.Sms.READ, read ? 1 : 0);
- values.put(Telephony.Sms.SEEN, seen ? 1 : 0);
- values.put(Telephony.Sms.SUBJECT, subject);
- values.put(Telephony.Sms.BODY, body);
- if (OsUtil.isAtLeastL_MR1()) {
- values.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
- }
- if (status != Telephony.Sms.STATUS_NONE) {
- values.put(Telephony.Sms.STATUS, status);
- }
- if (type != Telephony.Sms.MESSAGE_TYPE_ALL) {
- values.put(Telephony.Sms.TYPE, type);
- }
- if (threadId != -1L) {
- values.put(Telephony.Sms.THREAD_ID, threadId);
- }
- return resolver.insert(uri, values);
- }
-
- // Insert an SMS message to telephony
- public static Uri insertSmsMessage(final Context context, final Uri uri, final int subId,
- final String dest, final String text, final long timestamp, final int status,
- final int type, final long threadId) {
- Uri response = null;
- try {
- response = addMessageToUri(context.getContentResolver(), uri, subId, dest,
- text, null /* subject */, timestamp, true /* read */,
- true /* seen */, status, type, threadId);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Mmsutils: Inserted SMS message into telephony (type = " + type + ")"
- + ", uri: " + response);
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsUtils: persist sms message failure " + e, e);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: persist sms message failure " + e, e);
- }
- return response;
- }
-
- // Update SMS message type in telephony; returns true if it succeeded.
- public static boolean updateSmsMessageSendingStatus(final Context context, final Uri uri,
- final int type, final long date) {
- try {
- final ContentResolver resolver = context.getContentResolver();
- final ContentValues values = new ContentValues(2);
-
- values.put(Telephony.Sms.TYPE, type);
- values.put(Telephony.Sms.DATE, date);
- final int cnt = resolver.update(uri, values, null, null);
- if (cnt == 1) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Mmsutils: Updated sending SMS " + uri + "; type = " + type
- + ", date = " + date + " (millis since epoch)");
- }
- return true;
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsUtils: update sms message failure " + e, e);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: update sms message failure " + e, e);
- }
- return false;
- }
-
- // Persist a sent MMS message in telephony
- private static Uri insertSendReq(final Context context, final GenericPdu pdu, final int subId,
- final String subPhoneNumber) {
- final PduPersister persister = PduPersister.getPduPersister(context);
- Uri uri = null;
- try {
- // Persist the PDU
- uri = persister.persist(
- pdu,
- Mms.Sent.CONTENT_URI,
- subId,
- subPhoneNumber,
- null/*preOpenedFiles*/);
- // Update mms table to reflect sent messages are always seen and read
- final ContentValues values = new ContentValues(1);
- values.put(Mms.READ, 1);
- values.put(Mms.SEEN, 1);
- SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null);
- } catch (final MmsException e) {
- LogUtil.e(TAG, "MmsUtils: persist mms sent message failure " + e, e);
- }
- return uri;
- }
-
- // Persist a received MMS message in telephony
- public static Uri insertReceivedMmsMessage(final Context context,
- final RetrieveConf retrieveConf, final int subId, final String subPhoneNumber,
- final long receivedTimestampInSeconds, final String contentLocation) {
- final PduPersister persister = PduPersister.getPduPersister(context);
- Uri uri = null;
- try {
- uri = persister.persist(
- retrieveConf,
- Mms.Inbox.CONTENT_URI,
- subId,
- subPhoneNumber,
- null/*preOpenedFiles*/);
-
- final ContentValues values = new ContentValues(2);
- // Update mms table with local time instead of PDU time
- values.put(Mms.DATE, receivedTimestampInSeconds);
- // Also update the content location field from NotificationInd so that
- // wap push dedup would work even after the wap push is deleted
- values.put(Mms.CONTENT_LOCATION, contentLocation);
- SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Inserted MMS message into telephony, uri: " + uri);
- }
- } catch (final MmsException e) {
- LogUtil.e(TAG, "MmsUtils: persist mms received message failure " + e, e);
- // Just returns empty uri to RetrieveMmsRequest, which triggers a permanent failure
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsUtils: update mms received message failure " + e, e);
- // Time update failure is ignored.
- }
- return uri;
- }
-
- // Update MMS message type in telephony; returns true if it succeeded.
- public static boolean updateMmsMessageSendingStatus(final Context context, final Uri uri,
- final int box, final long timestampInMillis) {
- try {
- final ContentResolver resolver = context.getContentResolver();
- final ContentValues values = new ContentValues();
-
- final long timestampInSeconds = timestampInMillis / 1000L;
- values.put(Telephony.Mms.MESSAGE_BOX, box);
- values.put(Telephony.Mms.DATE, timestampInSeconds);
- final int cnt = resolver.update(uri, values, null, null);
- if (cnt == 1) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Mmsutils: Updated sending MMS " + uri + "; box = " + box
- + ", date = " + timestampInSeconds + " (secs since epoch)");
- }
- return true;
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsUtils: update mms message failure " + e, e);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: update mms message failure " + e, e);
- }
- return false;
- }
-
- /**
- * Parse values from a received sms message
- *
- * @param context
- * @param msgs The received sms message content
- * @param error The received sms error
- * @return Parsed values from the message
- */
- public static ContentValues parseReceivedSmsMessage(
- final Context context, final SmsMessage[] msgs, final int error) {
- final SmsMessage sms = msgs[0];
- final ContentValues values = new ContentValues();
-
- values.put(Sms.ADDRESS, sms.getDisplayOriginatingAddress());
- values.put(Sms.BODY, buildMessageBodyFromPdus(msgs));
- if (MmsUtils.hasSmsDateSentColumn()) {
- // TODO:: The boxing here seems unnecessary.
- values.put(Sms.DATE_SENT, Long.valueOf(sms.getTimestampMillis()));
- }
- values.put(Sms.PROTOCOL, sms.getProtocolIdentifier());
- if (sms.getPseudoSubject().length() > 0) {
- values.put(Sms.SUBJECT, sms.getPseudoSubject());
- }
- values.put(Sms.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0);
- values.put(Sms.SERVICE_CENTER, sms.getServiceCenterAddress());
- // Error code
- values.put(Sms.ERROR_CODE, error);
-
- return values;
- }
-
- // Some providers send formfeeds in their messages. Convert those formfeeds to newlines.
- private static String replaceFormFeeds(final String s) {
- return s == null ? "" : s.replace('\f', '\n');
- }
-
- // Parse the message body from message PDUs
- private static String buildMessageBodyFromPdus(final SmsMessage[] msgs) {
- if (msgs.length == 1) {
- // There is only one part, so grab the body directly.
- return replaceFormFeeds(msgs[0].getDisplayMessageBody());
- } else {
- // Build up the body from the parts.
- final StringBuilder body = new StringBuilder();
- for (final SmsMessage msg : msgs) {
- try {
- // getDisplayMessageBody() can NPE if mWrappedMessage inside is null.
- body.append(msg.getDisplayMessageBody());
- } catch (final NullPointerException e) {
- // Nothing to do
- }
- }
- return replaceFormFeeds(body.toString());
- }
- }
-
- // Parse the message date
- public static Long getMessageDate(final SmsMessage sms, long now) {
- // Use now for the timestamp to avoid confusion with clock
- // drift between the handset and the SMSC.
- // Check to make sure the system is giving us a non-bogus time.
- final Calendar buildDate = new GregorianCalendar(2011, 8, 18); // 18 Sep 2011
- final Calendar nowDate = new GregorianCalendar();
- nowDate.setTimeInMillis(now);
- if (nowDate.before(buildDate)) {
- // It looks like our system clock isn't set yet because the current time right now
- // is before an arbitrary time we made this build. Instead of inserting a bogus
- // receive time in this case, use the timestamp of when the message was sent.
- now = sms.getTimestampMillis();
- }
- return now;
- }
-
- /**
- * cleanseMmsSubject will take a subject that's says, "<Subject: no subject>", and return
- * a null string. Otherwise it will return the original subject string.
- * @param resources So the function can grab string resources
- * @param subject the raw subject
- * @return
- */
- public static String cleanseMmsSubject(final Resources resources, final String subject) {
- if (TextUtils.isEmpty(subject)) {
- return null;
- }
- if (sNoSubjectStrings == null) {
- sNoSubjectStrings =
- resources.getStringArray(R.array.empty_subject_strings);
- }
- for (final String noSubjectString : sNoSubjectStrings) {
- if (subject.equalsIgnoreCase(noSubjectString)) {
- return null;
- }
- }
- return subject;
- }
-
- // return a semicolon separated list of phone numbers from a smsto: uri.
- public static String getSmsRecipients(final Uri uri) {
- String recipients = uri.getSchemeSpecificPart();
- final int pos = recipients.indexOf('?');
- if (pos != -1) {
- recipients = recipients.substring(0, pos);
- }
- recipients = replaceUnicodeDigits(recipients).replace(',', ';');
- return recipients;
- }
-
- // This function was lifted from Telephony.PhoneNumberUtils because it was @hide
- /**
- * Replace arabic/unicode digits with decimal digits.
- * @param number
- * the number to be normalized.
- * @return the replaced number.
- */
- private static String replaceUnicodeDigits(final String number) {
- final StringBuilder normalizedDigits = new StringBuilder(number.length());
- for (final char c : number.toCharArray()) {
- final int digit = Character.digit(c, 10);
- if (digit != -1) {
- normalizedDigits.append(digit);
- } else {
- normalizedDigits.append(c);
- }
- }
- return normalizedDigits.toString();
- }
-
- /**
- * @return Whether the data roaming is enabled
- */
- private static boolean isDataRoamingEnabled() {
- boolean dataRoamingEnabled = false;
- final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
- if (OsUtil.isAtLeastJB_MR1()) {
- dataRoamingEnabled = (Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0);
- } else {
- dataRoamingEnabled = (Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0);
- }
- return dataRoamingEnabled;
- }
-
- /**
- * @return Whether to auto retrieve MMS
- */
- public static boolean allowMmsAutoRetrieve(final int subId) {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
- final boolean autoRetrieve = prefs.getBoolean(
- resources.getString(R.string.auto_retrieve_mms_pref_key),
- resources.getBoolean(R.bool.auto_retrieve_mms_pref_default));
- if (autoRetrieve) {
- final boolean autoRetrieveInRoaming = prefs.getBoolean(
- resources.getString(R.string.auto_retrieve_mms_when_roaming_pref_key),
- resources.getBoolean(R.bool.auto_retrieve_mms_when_roaming_pref_default));
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- if ((autoRetrieveInRoaming && phoneUtils.isDataRoamingEnabled())
- || !phoneUtils.isRoaming()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Parse the message row id from a message Uri.
- *
- * @param messageUri The input Uri
- * @return The message row id if valid, otherwise -1
- */
- public static long parseRowIdFromMessageUri(final Uri messageUri) {
- try {
- if (messageUri != null) {
- return ContentUris.parseId(messageUri);
- }
- } catch (final UnsupportedOperationException e) {
- // Nothing to do
- } catch (final NumberFormatException e) {
- // Nothing to do
- }
- return -1;
- }
-
- public static SmsMessage getSmsMessageFromDeliveryReport(final Intent intent) {
- final byte[] pdu = intent.getByteArrayExtra("pdu");
- return SmsMessage.createFromPdu(pdu);
- }
-
- /**
- * Update the status and date_sent column of sms message in telephony provider
- *
- * @param smsMessageUri
- * @param status
- * @param timeSentInMillis
- */
- public static void updateSmsStatusAndDateSent(final Uri smsMessageUri, final int status,
- final long timeSentInMillis) {
- if (smsMessageUri == null) {
- return;
- }
- final ContentValues values = new ContentValues();
- values.put(Sms.STATUS, status);
- if (MmsUtils.hasSmsDateSentColumn()) {
- values.put(Sms.DATE_SENT, timeSentInMillis);
- }
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- resolver.update(smsMessageUri, values, null/*where*/, null/*selectionArgs*/);
- }
-
- /**
- * Get the SQL selection statement for matching messages with media.
- *
- * Example for MMS part table:
- * "((ct LIKE 'image/%')
- * OR (ct LIKE 'video/%')
- * OR (ct LIKE 'audio/%')
- * OR (ct='application/ogg'))
- *
- * @param contentTypeColumn The content-type column name
- * @return The SQL selection statement for matching media types: image, video, audio
- */
- public static String getMediaTypeSelectionSql(final String contentTypeColumn) {
- return String.format(
- Locale.US,
- "((%s LIKE '%s') OR (%s LIKE '%s') OR (%s LIKE '%s') OR (%s='%s'))",
- contentTypeColumn,
- "image/%",
- contentTypeColumn,
- "video/%",
- contentTypeColumn,
- "audio/%",
- contentTypeColumn,
- ContentType.AUDIO_OGG);
- }
-
- // Max number of operands per SQL query for deleting SMS messages
- public static final int MAX_IDS_PER_QUERY = 128;
-
- /**
- * Delete MMS messages with media parts.
- *
- * Because the telephony provider constraints, we can't use JOIN and delete messages in one
- * shot. We have to do a query first and then batch delete the messages based on IDs.
- *
- * @return The count of messages deleted.
- */
- public static int deleteMediaMessages() {
- // Do a query first
- //
- // The WHERE clause has two parts:
- // The first part is to select the exact same types of MMS messages as when we import them
- // (so that we don't delete messages that are not in local database)
- // The second part is to select MMS with media parts, including image, video and audio
- final String selection = String.format(
- Locale.US,
- "%s AND (%s IN (SELECT %s FROM part WHERE %s))",
- getMmsTypeSelectionSql(),
- Mms._ID,
- Mms.Part.MSG_ID,
- getMediaTypeSelectionSql(Mms.Part.CONTENT_TYPE));
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final Cursor cursor = resolver.query(Mms.CONTENT_URI,
- new String[]{ Mms._ID },
- selection,
- null/*selectionArgs*/,
- null/*sortOrder*/);
- int deleted = 0;
- if (cursor != null) {
- final long[] messageIds = new long[cursor.getCount()];
- try {
- int i = 0;
- while (cursor.moveToNext()) {
- messageIds[i++] = cursor.getLong(0);
- }
- } finally {
- cursor.close();
- }
- final int totalIds = messageIds.length;
- if (totalIds > 0) {
- // Batch delete the messages using IDs
- // We don't want to send all IDs at once since there is a limit on SQL statement
- for (int start = 0; start < totalIds; start += MAX_IDS_PER_QUERY) {
- final int end = Math.min(start + MAX_IDS_PER_QUERY, totalIds); // excluding
- final int count = end - start;
- final String batchSelection = String.format(
- Locale.US,
- "%s IN %s",
- Mms._ID,
- getSqlInOperand(count));
- final String[] batchSelectionArgs =
- getSqlInOperandArgs(messageIds, start, count);
- final int deletedForBatch = resolver.delete(
- Mms.CONTENT_URI,
- batchSelection,
- batchSelectionArgs);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "deleteMediaMessages: deleting IDs = "
- + Joiner.on(',').skipNulls().join(batchSelectionArgs)
- + ", deleted = " + deletedForBatch);
- }
- deleted += deletedForBatch;
- }
- }
- }
- return deleted;
- }
-
- /**
- * Get the (?,?,...) thing for the SQL IN operator by a count
- *
- * @param count
- * @return
- */
- public static String getSqlInOperand(final int count) {
- if (count <= 0) {
- return null;
- }
- final StringBuilder sb = new StringBuilder();
- sb.append("(?");
- for (int i = 0; i < count - 1; i++) {
- sb.append(",?");
- }
- sb.append(")");
- return sb.toString();
- }
-
- /**
- * Get the args for SQL IN operator from a long ID array
- *
- * @param ids The original long id array
- * @param start Start of the ids to fill the args
- * @param count Number of ids to pack
- * @return The long array with the id args
- */
- private static String[] getSqlInOperandArgs(
- final long[] ids, final int start, final int count) {
- if (count <= 0) {
- return null;
- }
- final String[] args = new String[count];
- for (int i = 0; i < count; i++) {
- args[i] = Long.toString(ids[start + i]);
- }
- return args;
- }
-
- /**
- * Delete SMS and MMS messages that are earlier than a specific timestamp
- *
- * @param cutOffTimestampInMillis The cut-off timestamp
- * @return Total number of messages deleted.
- */
- public static int deleteMessagesOlderThan(final long cutOffTimestampInMillis) {
- int deleted = 0;
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- // Delete old SMS
- final String smsSelection = String.format(
- Locale.US,
- "%s AND (%s<=%d)",
- getSmsTypeSelectionSql(),
- Sms.DATE,
- cutOffTimestampInMillis);
- deleted += resolver.delete(Sms.CONTENT_URI, smsSelection, null/*selectionArgs*/);
- // Delete old MMS
- final String mmsSelection = String.format(
- Locale.US,
- "%s AND (%s<=%d)",
- getMmsTypeSelectionSql(),
- Mms.DATE,
- cutOffTimestampInMillis / 1000L);
- deleted += resolver.delete(Mms.CONTENT_URI, mmsSelection, null/*selectionArgs*/);
- return deleted;
- }
-
- /**
- * Update the read status of SMS/MMS messages by thread and timestamp
- *
- * @param threadId The thread of sms/mms to change
- * @param timestampInMillis Change the status before this timestamp
- */
- public static void updateSmsReadStatus(final long threadId, final long timestampInMillis) {
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final ContentValues values = new ContentValues();
- values.put("read", 1);
- values.put("seen", 1); /* If you read it you saw it */
- final String smsSelection = String.format(
- Locale.US,
- "%s=%d AND %s<=%d AND %s=0",
- Sms.THREAD_ID,
- threadId,
- Sms.DATE,
- timestampInMillis,
- Sms.READ);
- resolver.update(
- Sms.CONTENT_URI,
- values,
- smsSelection,
- null/*selectionArgs*/);
- final String mmsSelection = String.format(
- Locale.US,
- "%s=%d AND %s<=%d AND %s=0",
- Mms.THREAD_ID,
- threadId,
- Mms.DATE,
- timestampInMillis / 1000L,
- Mms.READ);
- resolver.update(
- Mms.CONTENT_URI,
- values,
- mmsSelection,
- null/*selectionArgs*/);
- }
-
- /**
- * Update the read status of a single MMS message by its URI
- *
- * @param mmsUri
- * @param read
- */
- public static void updateReadStatusForMmsMessage(final Uri mmsUri, final boolean read) {
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final ContentValues values = new ContentValues();
- values.put(Mms.READ, read ? 1 : 0);
- resolver.update(mmsUri, values, null/*where*/, null/*selectionArgs*/);
- }
-
- public static class AttachmentInfo {
- public String mUrl;
- public String mContentType;
- public int mWidth;
- public int mHeight;
- }
-
- /**
- * Convert byte array to Java String using a charset name
- *
- * @param bytes
- * @param charsetName
- * @return
- */
- public static String bytesToString(final byte[] bytes, final String charsetName) {
- if (bytes == null) {
- return null;
- }
- try {
- return new String(bytes, charsetName);
- } catch (final UnsupportedEncodingException e) {
- LogUtil.e(TAG, "MmsUtils.bytesToString: " + e, e);
- return new String(bytes);
- }
- }
-
- /**
- * Convert a Java String to byte array using a charset name
- *
- * @param string
- * @param charsetName
- * @return
- */
- public static byte[] stringToBytes(final String string, final String charsetName) {
- if (string == null) {
- return null;
- }
- try {
- return string.getBytes(charsetName);
- } catch (final UnsupportedEncodingException e) {
- LogUtil.e(TAG, "MmsUtils.stringToBytes: " + e, e);
- return string.getBytes();
- }
- }
-
- private static final String[] TEST_DATE_SENT_PROJECTION = new String[] { Sms.DATE_SENT };
- private static Boolean sHasSmsDateSentColumn = null;
- /**
- * Check if date_sent column exists on ICS and above devices. We need to do a test
- * query to figure that out since on some ICS+ devices, somehow the date_sent column does
- * not exist. http://b/17629135 tracks the associated compliance test.
- *
- * @return Whether "date_sent" column exists in sms table
- */
- public static boolean hasSmsDateSentColumn() {
- if (sHasSmsDateSentColumn == null) {
- Cursor cursor = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- cursor = SqliteWrapper.query(
- context,
- resolver,
- Sms.CONTENT_URI,
- TEST_DATE_SENT_PROJECTION,
- null/*selection*/,
- null/*selectionArgs*/,
- Sms.DATE_SENT + " ASC LIMIT 1");
- sHasSmsDateSentColumn = true;
- } catch (final SQLiteException e) {
- LogUtil.w(TAG, "date_sent in sms table does not exist", e);
- sHasSmsDateSentColumn = false;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- return sHasSmsDateSentColumn;
- }
-
- private static final String[] TEST_CARRIERS_PROJECTION =
- new String[] { Telephony.Carriers.MMSC };
- private static Boolean sUseSystemApn = null;
- /**
- * Check if we can access the APN data in the Telephony provider. Access was restricted in
- * JB MR1 (and some JB MR2) devices. If we can't access the APN, we have to fall back and use
- * a private table in our own app.
- *
- * @return Whether we can access the system APN table
- */
- public static boolean useSystemApnTable() {
- if (sUseSystemApn == null) {
- Cursor cursor = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- cursor = SqliteWrapper.query(
- context,
- resolver,
- Telephony.Carriers.CONTENT_URI,
- TEST_CARRIERS_PROJECTION,
- null/*selection*/,
- null/*selectionArgs*/,
- null);
- sUseSystemApn = true;
- } catch (final SecurityException e) {
- LogUtil.w(TAG, "Can't access system APN, using internal table", e);
- sUseSystemApn = false;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- return sUseSystemApn;
- }
-
- // For the internal debugger only
- public static void setUseSystemApnTable(final boolean turnOn) {
- if (!turnOn) {
- // We're not turning on to the system table. Instead, we're using our internal table.
- final int osVersion = OsUtil.getApiVersion();
- if (osVersion != android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
- // We're turning on local APNs on a device where we wouldn't normally have the
- // local APN table. Build it here.
-
- final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
-
- // Do we already have the table?
- Cursor cursor = null;
- try {
- cursor = database.query(ApnDatabase.APN_TABLE,
- ApnDatabase.APN_PROJECTION,
- null, null, null, null, null, null);
- } catch (final Exception e) {
- // Apparently there's no table, create it now.
- ApnDatabase.forceBuildAndLoadApnTables();
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
- sUseSystemApn = turnOn;
- }
-
- /**
- * Checks if we should dump sms, based on both the setting and the global debug
- * flag
- *
- * @return if dump sms is enabled
- */
- public static boolean isDumpSmsEnabled() {
- if (!DebugUtils.isDebugEnabled()) {
- return false;
- }
- return getDumpSmsOrMmsPref(R.string.dump_sms_pref_key, R.bool.dump_sms_pref_default);
- }
-
- /**
- * Checks if we should dump mms, based on both the setting and the global debug
- * flag
- *
- * @return if dump mms is enabled
- */
- public static boolean isDumpMmsEnabled() {
- if (!DebugUtils.isDebugEnabled()) {
- return false;
- }
- return getDumpSmsOrMmsPref(R.string.dump_mms_pref_key, R.bool.dump_mms_pref_default);
- }
-
- /**
- * Load the value of dump sms or mms setting preference
- */
- private static boolean getDumpSmsOrMmsPref(final int prefKeyRes, final int defaultKeyRes) {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- final String key = resources.getString(prefKeyRes);
- final boolean defaultValue = resources.getBoolean(defaultKeyRes);
- return prefs.getBoolean(key, defaultValue);
- }
-
- public static final Uri MMS_PART_CONTENT_URI = Uri.parse("content://mms/part");
-
- /**
- * Load MMS from telephony
- *
- * @param mmsUri The MMS pdu Uri
- * @return A memory copy of the MMS pdu including parts (but not addresses)
- */
- public static DatabaseMessages.MmsMessage loadMms(final Uri mmsUri) {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- DatabaseMessages.MmsMessage mms = null;
- Cursor cursor = null;
- // Load pdu first
- try {
- cursor = SqliteWrapper.query(context, resolver,
- mmsUri,
- DatabaseMessages.MmsMessage.getProjection(),
- null/*selection*/, null/*selectionArgs*/, null/*sortOrder*/);
- if (cursor != null && cursor.moveToFirst()) {
- mms = DatabaseMessages.MmsMessage.get(cursor);
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsLoader: query pdu failure: " + e, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- if (mms == null) {
- return null;
- }
- // Load parts except SMIL
- // TODO: we may need to load SMIL part in the future.
- final long rowId = MmsUtils.parseRowIdFromMessageUri(mmsUri);
- final String selection = String.format(
- Locale.US,
- "%s != '%s' AND %s = ?",
- Mms.Part.CONTENT_TYPE,
- ContentType.APP_SMIL,
- Mms.Part.MSG_ID);
- cursor = null;
- try {
- cursor = SqliteWrapper.query(context, resolver,
- MMS_PART_CONTENT_URI,
- DatabaseMessages.MmsPart.PROJECTION,
- selection,
- new String[] { Long.toString(rowId) },
- null/*sortOrder*/);
- if (cursor != null) {
- while (cursor.moveToNext()) {
- mms.addPart(DatabaseMessages.MmsPart.get(cursor, true/*loadMedia*/));
- }
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsLoader: query parts failure: " + e, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return mms;
- }
-
- /**
- * Get the sender of an MMS message
- *
- * @param recipients The recipient list of the message
- * @param mmsUri The pdu uri of the MMS
- * @return The sender phone number of the MMS
- */
- public static String getMmsSender(final List<String> recipients, final String mmsUri) {
- final Context context = Factory.get().getApplicationContext();
- // We try to avoid the database query.
- // If this is a 1v1 conv., then the other party is the sender
- if (recipients != null && recipients.size() == 1) {
- return recipients.get(0);
- }
- // Otherwise, we have to query the MMS addr table for sender address
- // This should only be done for a received group mms message
- final Cursor cursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Uri.withAppendedPath(Uri.parse(mmsUri), "addr"),
- new String[] { Mms.Addr.ADDRESS, Mms.Addr.CHARSET },
- Mms.Addr.TYPE + "=" + PduHeaders.FROM,
- null/*selectionArgs*/,
- null/*sortOrder*/);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- return DatabaseMessages.MmsAddr.get(cursor);
- }
- } finally {
- cursor.close();
- }
- }
- return null;
- }
-
- public static int bugleStatusForMms(final boolean isOutgoing, final boolean isNotification,
- final int messageBox) {
- int bugleStatus = MessageData.BUGLE_STATUS_UNKNOWN;
- // For a message we sync either
- if (isOutgoing) {
- if (messageBox == Mms.MESSAGE_BOX_OUTBOX || messageBox == Mms.MESSAGE_BOX_FAILED) {
- // Not sent counts as failed and available for manual resend
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_FAILED;
- } else {
- // Otherwise outgoing message is complete
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
- }
- } else if (isNotification) {
- // Incoming MMS notifications we sync count as failed and available for manual download
- bugleStatus = MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD;
- } else {
- // Other incoming MMS messages are complete
- bugleStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE;
- }
- return bugleStatus;
- }
-
- public static MessageData createMmsMessage(final DatabaseMessages.MmsMessage mms,
- final String conversationId, final String participantId, final String selfId,
- final int bugleStatus) {
- Assert.notNull(mms);
- final boolean isNotification = (mms.mMmsMessageType ==
- PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
- final int rawMmsStatus = (bugleStatus < MessageData.BUGLE_STATUS_FIRST_INCOMING
- ? mms.mRetrieveStatus : mms.mResponseStatus);
-
- final MessageData message = MessageData.createMmsMessage(mms.getUri(),
- participantId, selfId, conversationId, isNotification, bugleStatus,
- mms.mContentLocation, mms.mTransactionId, mms.mPriority, mms.mSubject,
- mms.mSeen, mms.mRead, mms.getSize(), rawMmsStatus,
- mms.mExpiryInMillis, mms.mSentTimestampInMillis, mms.mTimestampInMillis);
-
- for (final DatabaseMessages.MmsPart part : mms.mParts) {
- final MessagePartData messagePart = MmsUtils.createMmsMessagePart(part);
- // Import media and text parts (skip SMIL and others)
- if (messagePart != null) {
- message.addPart(messagePart);
- }
- }
-
- if (!message.getParts().iterator().hasNext()) {
- message.addPart(MessagePartData.createEmptyMessagePart());
- }
-
- return message;
- }
-
- public static MessagePartData createMmsMessagePart(final DatabaseMessages.MmsPart part) {
- MessagePartData messagePart = null;
- if (part.isText()) {
- final int mmsTextLengthLimit =
- BugleGservices.get().getInt(BugleGservicesKeys.MMS_TEXT_LIMIT,
- BugleGservicesKeys.MMS_TEXT_LIMIT_DEFAULT);
- String text = part.mText;
- if (text != null && text.length() > mmsTextLengthLimit) {
- // Limit the text to a reasonable value. We ran into a situation where a vcard
- // with a photo was sent as plain text. The massive amount of text caused the
- // app to hang, ANR, and eventually crash in native text code.
- text = text.substring(0, mmsTextLengthLimit);
- }
- messagePart = MessagePartData.createTextMessagePart(text);
- } else if (part.isMedia()) {
- messagePart = MessagePartData.createMediaMessagePart(part.mContentType,
- part.getDataUri(), MessagePartData.UNSPECIFIED_SIZE,
- MessagePartData.UNSPECIFIED_SIZE);
- }
- return messagePart;
- }
-
- public static class StatusPlusUri {
- // The request status to be as the result of the operation
- // e.g. MMS_REQUEST_MANUAL_RETRY
- public final int status;
- // The raw telephony status
- public final int rawStatus;
- // The raw telephony URI
- public final Uri uri;
- // The operation result code from system api invocation (sent by system)
- // or mapped from internal exception (sent by app)
- public final int resultCode;
-
- public StatusPlusUri(final int status, final int rawStatus, final Uri uri) {
- this.status = status;
- this.rawStatus = rawStatus;
- this.uri = uri;
- resultCode = MessageData.UNKNOWN_RESULT_CODE;
- }
-
- public StatusPlusUri(final int status, final int rawStatus, final Uri uri,
- final int resultCode) {
- this.status = status;
- this.rawStatus = rawStatus;
- this.uri = uri;
- this.resultCode = resultCode;
- }
- }
-
- public static class SendReqResp {
- public SendReq mSendReq;
- public SendConf mSendConf;
-
- public SendReqResp(final SendReq sendReq, final SendConf sendConf) {
- mSendReq = sendReq;
- mSendConf = sendConf;
- }
- }
-
- /**
- * Returned when sending/downloading MMS via platform APIs. In that case, we have to wait to
- * receive the pending intent to determine status.
- */
- public static final StatusPlusUri STATUS_PENDING = new StatusPlusUri(-1, -1, null);
-
- public static StatusPlusUri downloadMmsMessage(final Context context, final Uri notificationUri,
- final int subId, final String subPhoneNumber, final String transactionId,
- final String contentLocation, final boolean autoDownload,
- final long receivedTimestampInSeconds, Bundle extras) {
- if (TextUtils.isEmpty(contentLocation)) {
- LogUtil.e(TAG, "MmsUtils: Download from empty content location URL");
- return new StatusPlusUri(
- MMS_REQUEST_NO_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, null);
- }
- if (!isMmsDataAvailable(subId)) {
- LogUtil.e(TAG,
- "MmsUtils: failed to download message, no data available");
- return new StatusPlusUri(MMS_REQUEST_MANUAL_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
- null,
- SmsManager.MMS_ERROR_NO_DATA_NETWORK);
- }
- int status = MMS_REQUEST_MANUAL_RETRY;
- try {
- RetrieveConf retrieveConf = null;
- if (DebugUtils.isDebugEnabled() &&
- MediaScratchFileProvider
- .isMediaScratchSpaceUri(Uri.parse(contentLocation))) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Reading MMS from dump file: " + contentLocation);
- }
- final String fileName = Uri.parse(contentLocation).getPathSegments().get(1);
- final byte[] data = DebugUtils.receiveFromDumpFile(fileName);
- retrieveConf = receiveFromDumpFile(data);
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Downloading MMS via MMS lib API; notification "
- + "message: " + notificationUri);
- }
- if (OsUtil.isAtLeastL_MR1()) {
- if (subId < 0) {
- LogUtil.e(TAG, "MmsUtils: Incoming MMS came from unknown SIM");
- throw new MmsFailureException(MMS_REQUEST_NO_RETRY,
- "Message from unknown SIM");
- }
- } else {
- Assert.isTrue(subId == ParticipantData.DEFAULT_SELF_SUB_ID);
- }
- if (extras == null) {
- extras = new Bundle();
- }
- extras.putParcelable(DownloadMmsAction.EXTRA_NOTIFICATION_URI, notificationUri);
- extras.putInt(DownloadMmsAction.EXTRA_SUB_ID, subId);
- extras.putString(DownloadMmsAction.EXTRA_SUB_PHONE_NUMBER, subPhoneNumber);
- extras.putString(DownloadMmsAction.EXTRA_TRANSACTION_ID, transactionId);
- extras.putString(DownloadMmsAction.EXTRA_CONTENT_LOCATION, contentLocation);
- extras.putBoolean(DownloadMmsAction.EXTRA_AUTO_DOWNLOAD, autoDownload);
- extras.putLong(DownloadMmsAction.EXTRA_RECEIVED_TIMESTAMP,
- receivedTimestampInSeconds);
-
- MmsSender.downloadMms(context, subId, contentLocation, extras);
- return STATUS_PENDING; // Download happens asynchronously; no status to return
- }
- return insertDownloadedMessageAndSendResponse(context, notificationUri, subId,
- subPhoneNumber, transactionId, contentLocation, autoDownload,
- receivedTimestampInSeconds, retrieveConf);
-
- } catch (final MmsFailureException e) {
- LogUtil.e(TAG, "MmsUtils: failed to download message " + notificationUri, e);
- status = e.retryHint;
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "MmsUtils: failed to download message " + notificationUri, e);
- }
- return new StatusPlusUri(status, PDU_HEADER_VALUE_UNDEFINED, null);
- }
-
- public static StatusPlusUri insertDownloadedMessageAndSendResponse(final Context context,
- final Uri notificationUri, final int subId, final String subPhoneNumber,
- final String transactionId, final String contentLocation,
- final boolean autoDownload, final long receivedTimestampInSeconds,
- final RetrieveConf retrieveConf) {
- final byte[] transactionIdBytes = stringToBytes(transactionId, "UTF-8");
- Uri messageUri = null;
- int status = MMS_REQUEST_MANUAL_RETRY;
- int retrieveStatus = PDU_HEADER_VALUE_UNDEFINED;
-
- retrieveStatus = retrieveConf.getRetrieveStatus();
- if (retrieveStatus == PduHeaders.RETRIEVE_STATUS_OK) {
- status = MMS_REQUEST_SUCCEEDED;
- } else if (retrieveStatus >= PduHeaders.RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE &&
- retrieveStatus < PduHeaders.RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE) {
- status = MMS_REQUEST_AUTO_RETRY;
- } else {
- // else not meant to retry download
- status = MMS_REQUEST_NO_RETRY;
- LogUtil.e(TAG, "MmsUtils: failed to retrieve message; retrieveStatus: "
- + retrieveStatus);
- }
- final ContentValues values = new ContentValues(1);
- values.put(Mms.RETRIEVE_STATUS, retrieveConf.getRetrieveStatus());
- SqliteWrapper.update(context, context.getContentResolver(),
- notificationUri, values, null, null);
-
- if (status == MMS_REQUEST_SUCCEEDED) {
- // Send response of the notification
- if (autoDownload) {
- sendNotifyResponseForMmsDownload(context, subId, transactionIdBytes,
- contentLocation, PduHeaders.STATUS_RETRIEVED);
- } else {
- sendAcknowledgeForMmsDownload(context, subId, transactionIdBytes, contentLocation);
- }
-
- // Insert downloaded message into telephony
- final Uri inboxUri = MmsUtils.insertReceivedMmsMessage(context, retrieveConf, subId,
- subPhoneNumber, receivedTimestampInSeconds, contentLocation);
- messageUri = ContentUris.withAppendedId(Mms.CONTENT_URI, ContentUris.parseId(inboxUri));
- } else if (status == MMS_REQUEST_AUTO_RETRY) {
- // For a retry do nothing
- } else if (status == MMS_REQUEST_MANUAL_RETRY && autoDownload) {
- // Failure from autodownload - just treat like manual download
- sendNotifyResponseForMmsDownload(context, subId, transactionIdBytes,
- contentLocation, PduHeaders.STATUS_DEFERRED);
- }
- return new StatusPlusUri(status, retrieveStatus, messageUri);
- }
-
- /**
- * Send response for MMS download - catches and ignores errors
- */
- public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
- final byte[] transactionId, final String contentLocation, final int status) {
- try {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Sending M-NotifyResp.ind for received MMS, status: "
- + String.format("0x%X", status));
- }
- if (contentLocation == null) {
- LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; contentLocation is null");
- return;
- }
- if (transactionId == null) {
- LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; transaction id is null");
- return;
- }
- if (!isMmsDataAvailable(subId)) {
- LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; no data available");
- return;
- }
- MmsSender.sendNotifyResponseForMmsDownload(
- context, subId, transactionId, contentLocation, status);
- } catch (final MmsFailureException e) {
- LogUtil.e(TAG, "sendNotifyResponseForMmsDownload: failed to retrieve message " + e, e);
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "sendNotifyResponseForMmsDownload: failed to retrieve message " + e, e);
- }
- }
-
- /**
- * Send acknowledge for mms download - catched and ignores errors
- */
- public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
- final byte[] transactionId, final String contentLocation) {
- try {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Sending M-Acknowledge.ind for received MMS");
- }
- if (contentLocation == null) {
- LogUtil.w(TAG, "MmsUtils: Can't send AckInd; contentLocation is null");
- return;
- }
- if (transactionId == null) {
- LogUtil.w(TAG, "MmsUtils: Can't send AckInd; transaction id is null");
- return;
- }
- if (!isMmsDataAvailable(subId)) {
- LogUtil.w(TAG, "MmsUtils: Can't send AckInd; no data available");
- return;
- }
- MmsSender.sendAcknowledgeForMmsDownload(context, subId, transactionId, contentLocation);
- } catch (final MmsFailureException e) {
- LogUtil.e(TAG, "sendAcknowledgeForMmsDownload: failed to retrieve message " + e, e);
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "sendAcknowledgeForMmsDownload: failed to retrieve message " + e, e);
- }
- }
-
- /**
- * Try parsing a PDU without knowing the carrier. This is useful for importing
- * MMS or storing draft when carrier info is not available
- *
- * @param data The PDU data
- * @return Parsed PDU, null if failed to parse
- */
- private static GenericPdu parsePduForAnyCarrier(final byte[] data) {
- GenericPdu pdu = null;
- try {
- pdu = (new PduParser(data, true/*parseContentDisposition*/)).parse();
- } catch (final RuntimeException e) {
- LogUtil.d(TAG, "parsePduForAnyCarrier: Failed to parse PDU with content disposition",
- e);
- }
- if (pdu == null) {
- try {
- pdu = (new PduParser(data, false/*parseContentDisposition*/)).parse();
- } catch (final RuntimeException e) {
- LogUtil.d(TAG,
- "parsePduForAnyCarrier: Failed to parse PDU without content disposition",
- e);
- }
- }
- return pdu;
- }
-
- private static RetrieveConf receiveFromDumpFile(final byte[] data) throws MmsFailureException {
- final GenericPdu pdu = parsePduForAnyCarrier(data);
- if (pdu == null || !(pdu instanceof RetrieveConf)) {
- LogUtil.e(TAG, "receiveFromDumpFile: Parsing retrieved PDU failure");
- throw new MmsFailureException(MMS_REQUEST_MANUAL_RETRY, "Failed reading dump file");
- }
- return (RetrieveConf) pdu;
- }
-
- private static boolean isMmsDataAvailable(final int subId) {
- if (OsUtil.isAtLeastL_MR1()) {
- // L_MR1 above may support sending mms via wifi
- return true;
- }
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- return !phoneUtils.isAirplaneModeOn() && phoneUtils.isMobileDataEnabled();
- }
-
- private static boolean isSmsDataAvailable(final int subId) {
- if (OsUtil.isAtLeastL_MR1()) {
- // L_MR1 above may support sending sms via wifi
- return true;
- }
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- return !phoneUtils.isAirplaneModeOn();
- }
-
- public static boolean isMobileDataEnabled(final int subId) {
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- return phoneUtils.isMobileDataEnabled();
- }
-
- public static boolean isAirplaneModeOn(final int subId) {
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- return phoneUtils.isAirplaneModeOn();
- }
-
- public static StatusPlusUri sendMmsMessage(final Context context, final int subId,
- final Uri messageUri, final Bundle extras) {
- int status = MMS_REQUEST_MANUAL_RETRY;
- int rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- if (!isMmsDataAvailable(subId)) {
- LogUtil.w(TAG, "MmsUtils: failed to send message, no data available");
- return new StatusPlusUri(MMS_REQUEST_MANUAL_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
- messageUri,
- SmsManager.MMS_ERROR_NO_DATA_NETWORK);
- }
- final PduPersister persister = PduPersister.getPduPersister(context);
- try {
- final SendReq sendReq = (SendReq) persister.load(messageUri);
- if (sendReq == null) {
- LogUtil.w(TAG, "MmsUtils: Sending MMS was deleted; uri = " + messageUri);
- return new StatusPlusUri(MMS_REQUEST_NO_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, messageUri);
- }
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, String.format("MmsUtils: Sending MMS, message uri: %s", messageUri));
- }
- extras.putInt(SendMessageAction.KEY_SUB_ID, subId);
- MmsSender.sendMms(context, subId, messageUri, sendReq, extras);
- return STATUS_PENDING;
- } catch (final MmsFailureException e) {
- status = e.retryHint;
- rawStatus = e.rawStatus;
- LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: invalid message to send " + e, e);
- } catch (final MmsException e) {
- LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
- }
- // If we get here, some exception occurred
- return new StatusPlusUri(status, rawStatus, messageUri);
- }
-
- public static StatusPlusUri updateSentMmsMessageStatus(final Context context,
- final Uri messageUri, final SendConf sendConf) {
- int status = MMS_REQUEST_MANUAL_RETRY;
- final int respStatus = sendConf.getResponseStatus();
-
- final ContentValues values = new ContentValues(2);
- values.put(Mms.RESPONSE_STATUS, respStatus);
- final byte[] messageId = sendConf.getMessageId();
- if (messageId != null && messageId.length > 0) {
- values.put(Mms.MESSAGE_ID, PduPersister.toIsoString(messageId));
- }
- SqliteWrapper.update(context, context.getContentResolver(),
- messageUri, values, null, null);
- if (respStatus == PduHeaders.RESPONSE_STATUS_OK) {
- status = MMS_REQUEST_SUCCEEDED;
- } else if (respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE ||
- respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM ||
- respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) {
- status = MMS_REQUEST_AUTO_RETRY;
- } else {
- // else permanent failure
- LogUtil.e(TAG, "MmsUtils: failed to send message; respStatus = "
- + String.format("0x%X", respStatus));
- }
- return new StatusPlusUri(status, respStatus, messageUri);
- }
-
- public static void clearMmsStatus(final Context context, final Uri uri) {
- // Messaging application can leave invalid values in STATUS field of M-Notification.ind
- // messages. Take this opportunity to clear it.
- // Downloading status just kept in local db and not reflected into telephony.
- final ContentValues values = new ContentValues(1);
- values.putNull(Mms.STATUS);
- SqliteWrapper.update(context, context.getContentResolver(),
- uri, values, null, null);
- }
-
- // Selection for new dedup algorithm:
- // ((m_type<>130) OR (exp>NOW)) AND (date>NOW-7d) AND (date<NOW+7d) AND (ct_l=xxxxxx)
- // i.e. If it is NotificationInd and not expired or not NotificationInd
- // AND message is received with +/- 7 days from now
- // AND content location is the input URL
- private static final String DUP_NOTIFICATION_QUERY_SELECTION =
- "((" + Mms.MESSAGE_TYPE + "<>?) OR (" + Mms.EXPIRY + ">?)) AND ("
- + Mms.DATE + ">?) AND (" + Mms.DATE + "<?) AND (" + Mms.CONTENT_LOCATION +
- "=?)";
- // Selection for old behavior: only checks NotificationInd and its content location
- private static final String DUP_NOTIFICATION_QUERY_SELECTION_OLD =
- "(" + Mms.MESSAGE_TYPE + "=?) AND (" + Mms.CONTENT_LOCATION + "=?)";
-
- private static final int MAX_RETURN = 32;
- private static String[] getDupNotifications(final Context context, final NotificationInd nInd) {
- final byte[] rawLocation = nInd.getContentLocation();
- if (rawLocation != null) {
- final String location = new String(rawLocation);
- // We can not be sure if the content location of an MMS is globally and historically
- // unique. So we limit the dedup time within the last 7 days
- // (or configured by gservices remotely). If the same content location shows up after
- // that, we will download regardless. Duplicated message is better than no message.
- String selection;
- String[] selectionArgs;
- final long timeLimit = BugleGservices.get().getLong(
- BugleGservicesKeys.MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS,
- BugleGservicesKeys.MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS_DEFAULT);
- if (timeLimit > 0) {
- // New dedup algorithm
- selection = DUP_NOTIFICATION_QUERY_SELECTION;
- final long nowSecs = System.currentTimeMillis() / 1000;
- final long timeLowerBoundSecs = nowSecs - timeLimit;
- // Need upper bound to protect against clock change so that a message has a time
- // stamp in the future
- final long timeUpperBoundSecs = nowSecs + timeLimit;
- selectionArgs = new String[] {
- Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
- Long.toString(nowSecs),
- Long.toString(timeLowerBoundSecs),
- Long.toString(timeUpperBoundSecs),
- location
- };
- } else {
- // If time limit is 0, we revert back to old behavior in case the new
- // dedup algorithm behaves badly
- selection = DUP_NOTIFICATION_QUERY_SELECTION_OLD;
- selectionArgs = new String[] {
- Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
- location
- };
- }
- Cursor cursor = null;
- try {
- cursor = SqliteWrapper.query(
- context, context.getContentResolver(),
- Mms.CONTENT_URI, new String[] { Mms._ID },
- selection, selectionArgs, null);
- final int dupCount = cursor.getCount();
- if (dupCount > 0) {
- // We already received the same notification before.
- // Don't want to return too many dups. It is only for debugging.
- final int returnCount = dupCount < MAX_RETURN ? dupCount : MAX_RETURN;
- final String[] dups = new String[returnCount];
- for (int i = 0; cursor.moveToNext() && i < returnCount; i++) {
- dups[i] = cursor.getString(0);
- }
- return dups;
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "query failure: " + e, e);
- } finally {
- cursor.close();
- }
- }
- return null;
- }
-
- /**
- * Try parse the address using RFC822 format. If it fails to parse, then return the
- * original address
- *
- * @param address The MMS ind sender address to parse
- * @return The real address. If in RFC822 format, returns the correct email.
- */
- private static String parsePotentialRfc822EmailAddress(final String address) {
- if (address == null || !address.contains("@") || !address.contains("<")) {
- return address;
- }
- final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
- if (tokens != null && tokens.length > 0) {
- for (final Rfc822Token token : tokens) {
- if (token != null && !TextUtils.isEmpty(token.getAddress())) {
- return token.getAddress();
- }
- }
- }
- return address;
- }
-
- public static DatabaseMessages.MmsMessage processReceivedPdu(final Context context,
- final byte[] pushData, final int subId, final String subPhoneNumber) {
- // Parse data
-
- // Insert placeholder row to telephony and local db
- // Get raw PDU push-data from the message and parse it
- final PduParser parser = new PduParser(pushData,
- MmsConfig.get(subId).getSupportMmsContentDisposition());
- final GenericPdu pdu = parser.parse();
-
- if (null == pdu) {
- LogUtil.e(TAG, "Invalid PUSH data");
- return null;
- }
-
- final PduPersister p = PduPersister.getPduPersister(context);
- final int type = pdu.getMessageType();
-
- Uri messageUri = null;
- switch (type) {
- case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
- case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: {
- // TODO: Should this be commented out?
-// threadId = findThreadId(context, pdu, type);
-// if (threadId == -1) {
-// // The associated SendReq isn't found, therefore skip
-// // processing this PDU.
-// break;
-// }
-
-// Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true,
-// MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null);
-// // Update thread ID for ReadOrigInd & DeliveryInd.
-// ContentValues values = new ContentValues(1);
-// values.put(Mms.THREAD_ID, threadId);
-// SqliteWrapper.update(mContext, cr, uri, values, null, null);
- LogUtil.w(TAG, "Received unsupported WAP Push, type=" + type);
- break;
- }
- case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: {
- final NotificationInd nInd = (NotificationInd) pdu;
-
- if (MmsConfig.get(subId).getTransIdEnabled()) {
- final byte [] contentLocationTemp = nInd.getContentLocation();
- if ('=' == contentLocationTemp[contentLocationTemp.length - 1]) {
- final byte [] transactionIdTemp = nInd.getTransactionId();
- final byte [] contentLocationWithId =
- new byte [contentLocationTemp.length
- + transactionIdTemp.length];
- System.arraycopy(contentLocationTemp, 0, contentLocationWithId,
- 0, contentLocationTemp.length);
- System.arraycopy(transactionIdTemp, 0, contentLocationWithId,
- contentLocationTemp.length, transactionIdTemp.length);
- nInd.setContentLocation(contentLocationWithId);
- }
- }
- final String[] dups = getDupNotifications(context, nInd);
- if (dups == null) {
- // TODO: Do we handle Rfc822 Email Addresses?
- //final String contentLocation =
- // MmsUtils.bytesToString(nInd.getContentLocation(), "UTF-8");
- //final byte[] transactionId = nInd.getTransactionId();
- //final long messageSize = nInd.getMessageSize();
- //final long expiry = nInd.getExpiry();
- //final String transactionIdString =
- // MmsUtils.bytesToString(transactionId, "UTF-8");
-
- //final EncodedStringValue fromEncoded = nInd.getFrom();
- // An mms ind received from email address will have from address shown as
- // "John Doe <johndoe@foobar.com>" but the actual received message will only
- // have the email address. So let's try to parse the RFC822 format to get the
- // real email. Otherwise we will create two conversations for the MMS
- // notification and the actual MMS message if auto retrieve is disabled.
- //final String from = parsePotentialRfc822EmailAddress(
- // fromEncoded != null ? fromEncoded.getString() : null);
-
- Uri inboxUri = null;
- try {
- inboxUri = p.persist(pdu, Mms.Inbox.CONTENT_URI, subId, subPhoneNumber,
- null);
- messageUri = ContentUris.withAppendedId(Mms.CONTENT_URI,
- ContentUris.parseId(inboxUri));
- } catch (final MmsException e) {
- LogUtil.e(TAG, "Failed to save the data from PUSH: type=" + type, e);
- }
- } else {
- LogUtil.w(TAG, "Received WAP Push is a dup: " + Joiner.on(',').join(dups));
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.w(TAG, "Dup WAP Push url=" + new String(nInd.getContentLocation()));
- }
- }
- break;
- }
- default:
- LogUtil.e(TAG, "Received unrecognized WAP Push, type=" + type);
- }
-
- DatabaseMessages.MmsMessage mms = null;
- if (messageUri != null) {
- mms = MmsUtils.loadMms(messageUri);
- }
- return mms;
- }
-
- public static Uri insertSendingMmsMessage(final Context context, final List<String> recipients,
- final MessageData content, final int subId, final String subPhoneNumber,
- final long timestamp) {
- final SendReq sendReq = createMmsSendReq(
- context, subId, recipients.toArray(new String[recipients.size()]), content,
- DEFAULT_DELIVERY_REPORT_MODE,
- DEFAULT_READ_REPORT_MODE,
- DEFAULT_EXPIRY_TIME_IN_SECONDS,
- DEFAULT_PRIORITY,
- timestamp);
- Uri messageUri = null;
- if (sendReq != null) {
- final Uri outboxUri = MmsUtils.insertSendReq(context, sendReq, subId, subPhoneNumber);
- if (outboxUri != null) {
- messageUri = ContentUris.withAppendedId(Telephony.Mms.CONTENT_URI,
- ContentUris.parseId(outboxUri));
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Mmsutils: Inserted sending MMS message into telephony, uri: "
- + outboxUri);
- }
- } else {
- LogUtil.e(TAG, "insertSendingMmsMessage: failed to persist message into telephony");
- }
- }
- return messageUri;
- }
-
- public static MessageData readSendingMmsMessage(final Uri messageUri,
- final String conversationId, final String participantId, final String selfId) {
- MessageData message = null;
- if (messageUri != null) {
- final DatabaseMessages.MmsMessage mms = MmsUtils.loadMms(messageUri);
-
- // Make sure that the message has not been deleted from the Telephony DB
- if (mms != null) {
- // Transform the message
- message = MmsUtils.createMmsMessage(mms, conversationId, participantId, selfId,
- MessageData.BUGLE_STATUS_OUTGOING_RESENDING);
- }
- }
- return message;
- }
-
- /**
- * Create an MMS message with subject, text and image
- *
- * @return Both the M-Send.req and the M-Send.conf for processing in the caller
- * @throws MmsException
- */
- private static SendReq createMmsSendReq(final Context context, final int subId,
- final String[] recipients, final MessageData message,
- final boolean requireDeliveryReport, final boolean requireReadReport,
- final long expiryTime, final int priority, final long timestampMillis) {
- Assert.notNull(context);
- if (recipients == null || recipients.length < 1) {
- throw new IllegalArgumentException("MMS sendReq no recipient");
- }
-
- // Make a copy so we don't propagate changes to recipients to outside of this method
- final String[] recipientsCopy = new String[recipients.length];
- // Don't send phone number as is since some received phone number is malformed
- // for sending. We need to strip the separators.
- for (int i = 0; i < recipients.length; i++) {
- final String recipient = recipients[i];
- if (EmailAddress.isValidEmail(recipients[i])) {
- // Don't do stripping for emails
- recipientsCopy[i] = recipient;
- } else {
- recipientsCopy[i] = stripPhoneNumberSeparators(recipient);
- }
- }
-
- SendReq sendReq = null;
- try {
- sendReq = createSendReq(context, subId, recipientsCopy,
- message, requireDeliveryReport,
- requireReadReport, expiryTime, priority, timestampMillis);
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "InvalidHeaderValue creating sendReq PDU");
- } catch (final OutOfMemoryError e) {
- LogUtil.e(TAG, "Out of memory error creating sendReq PDU");
- }
- return sendReq;
- }
-
- /**
- * Stripping out the invalid characters in a phone number before sending
- * MMS. We only keep alphanumeric and '*', '#', '+'.
- */
- private static String stripPhoneNumberSeparators(final String phoneNumber) {
- if (phoneNumber == null) {
- return null;
- }
- final int len = phoneNumber.length();
- final StringBuilder ret = new StringBuilder(len);
- for (int i = 0; i < len; i++) {
- final char c = phoneNumber.charAt(i);
- if (Character.isLetterOrDigit(c) || c == '+' || c == '*' || c == '#') {
- ret.append(c);
- }
- }
- return ret.toString();
- }
-
- /**
- * Create M-Send.req for the MMS message to be sent.
- *
- * @return the M-Send.req
- * @throws InvalidHeaderValueException if there is any error in parsing the input
- */
- static SendReq createSendReq(final Context context, final int subId,
- final String[] recipients, final MessageData message,
- final boolean requireDeliveryReport,
- final boolean requireReadReport, final long expiryTime, final int priority,
- final long timestampMillis)
- throws InvalidHeaderValueException {
- final SendReq req = new SendReq();
- // From, per spec
- final String lineNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
- if (!TextUtils.isEmpty(lineNumber)) {
- req.setFrom(new EncodedStringValue(lineNumber));
- }
- // To
- final EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipients);
- if (encodedNumbers != null) {
- req.setTo(encodedNumbers);
- }
- // Subject
- if (!TextUtils.isEmpty(message.getMmsSubject())) {
- req.setSubject(new EncodedStringValue(message.getMmsSubject()));
- }
- // Date
- req.setDate(timestampMillis / 1000L);
- // Body
- final MmsInfo bodyInfo = MmsUtils.makePduBody(context, message, subId);
- req.setBody(bodyInfo.mPduBody);
- // Message size
- req.setMessageSize(bodyInfo.mMessageSize);
- // Message class
- req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
- // Expiry
- req.setExpiry(expiryTime);
- // Priority
- req.setPriority(priority);
- // Delivery report
- req.setDeliveryReport(requireDeliveryReport ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO);
- // Read report
- req.setReadReport(requireReadReport ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO);
- return req;
- }
-
- public static boolean isDeliveryReportRequired(final int subId) {
- if (!MmsConfig.get(subId).getSMSDeliveryReportsEnabled()) {
- return false;
- }
- final Context context = Factory.get().getApplicationContext();
- final Resources res = context.getResources();
- final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
- final String deliveryReportKey = res.getString(R.string.delivery_reports_pref_key);
- final boolean defaultValue = res.getBoolean(R.bool.delivery_reports_pref_default);
- return prefs.getBoolean(deliveryReportKey, defaultValue);
- }
-
- public static int sendSmsMessage(final String recipient, final String messageText,
- final Uri requestUri, final int subId,
- final String smsServiceCenter, final boolean requireDeliveryReport) {
- if (!isSmsDataAvailable(subId)) {
- LogUtil.w(TAG, "MmsUtils: can't send SMS without radio");
- return MMS_REQUEST_MANUAL_RETRY;
- }
- final Context context = Factory.get().getApplicationContext();
- int status = MMS_REQUEST_MANUAL_RETRY;
- try {
- // Send a single message
- final SendResult result = SmsSender.sendMessage(
- context,
- subId,
- recipient,
- messageText,
- smsServiceCenter,
- requireDeliveryReport,
- requestUri);
- if (!result.hasPending()) {
- // not timed out, check failures
- final int failureLevel = result.getHighestFailureLevel();
- switch (failureLevel) {
- case SendResult.FAILURE_LEVEL_NONE:
- status = MMS_REQUEST_SUCCEEDED;
- break;
- case SendResult.FAILURE_LEVEL_TEMPORARY:
- status = MMS_REQUEST_AUTO_RETRY;
- LogUtil.e(TAG, "MmsUtils: SMS temporary failure");
- break;
- case SendResult.FAILURE_LEVEL_PERMANENT:
- LogUtil.e(TAG, "MmsUtils: SMS permanent failure");
- break;
- }
- } else {
- // Timed out
- LogUtil.e(TAG, "MmsUtils: sending SMS timed out");
- }
- } catch (final Exception e) {
- LogUtil.e(TAG, "MmsUtils: failed to send SMS " + e, e);
- }
- return status;
- }
-
- /**
- * Delete SMS and MMS messages in a particular thread
- *
- * @return the number of messages deleted
- */
- public static int deleteThread(final long threadId, final long cutOffTimestampInMillis) {
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final Uri threadUri = ContentUris.withAppendedId(Telephony.Threads.CONTENT_URI, threadId);
- if (cutOffTimestampInMillis < Long.MAX_VALUE) {
- return resolver.delete(threadUri, Sms.DATE + "<=?",
- new String[] { Long.toString(cutOffTimestampInMillis) });
- } else {
- return resolver.delete(threadUri, null /* smsSelection */, null /* selectionArgs */);
- }
- }
-
- /**
- * Delete single SMS and MMS message
- *
- * @return number of rows deleted (should be 1 or 0)
- */
- public static int deleteMessage(final Uri messageUri) {
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- return resolver.delete(messageUri, null /* selection */, null /* selectionArgs */);
- }
-
- public static byte[] createDebugNotificationInd(final String fileName) {
- byte[] pduData = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- // Load the message file
- final byte[] data = DebugUtils.receiveFromDumpFile(fileName);
- final RetrieveConf retrieveConf = receiveFromDumpFile(data);
- // Create the notification
- final NotificationInd notification = new NotificationInd();
- final long expiry = System.currentTimeMillis() / 1000 + 600;
- notification.setTransactionId(fileName.getBytes());
- notification.setMmsVersion(retrieveConf.getMmsVersion());
- notification.setFrom(retrieveConf.getFrom());
- notification.setSubject(retrieveConf.getSubject());
- notification.setExpiry(expiry);
- notification.setMessageSize(data.length);
- notification.setMessageClass(retrieveConf.getMessageClass());
-
- final Uri.Builder builder = MediaScratchFileProvider.getUriBuilder();
- builder.appendPath(fileName);
- final Uri contentLocation = builder.build();
- notification.setContentLocation(contentLocation.toString().getBytes());
-
- // Serialize
- pduData = new PduComposer(context, notification).make();
- if (pduData == null || pduData.length < 1) {
- throw new IllegalArgumentException("Empty or zero length PDU data");
- }
- } catch (final MmsFailureException e) {
- // Nothing to do
- } catch (final InvalidHeaderValueException e) {
- // Nothing to do
- }
- return pduData;
- }
-
- public static int mapRawStatusToErrorResourceId(final int bugleStatus, final int rawStatus) {
- int stringResId = R.string.message_status_send_failed;
- switch (rawStatus) {
- case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED:
- case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID:
- stringResId = R.string.mms_failure_outgoing_service;
- break;
- case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED:
- case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED:
- case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED:
- stringResId = R.string.mms_failure_outgoing_address;
- break;
- case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT:
- case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT:
- stringResId = R.string.mms_failure_outgoing_corrupt;
- break;
- case PduHeaders.RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED:
- case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED:
- stringResId = R.string.mms_failure_outgoing_content;
- break;
- case PduHeaders.RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE:
- //case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND:
- //case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND:
- stringResId = R.string.mms_failure_outgoing_unsupported;
- break;
- case MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG:
- stringResId = R.string.mms_failure_outgoing_too_large;
- break;
- }
- return stringResId;
- }
-
- /**
- * The absence of a connection type.
- */
- public static final int TYPE_NONE = -1;
-
- public static int getConnectivityEventNetworkType(final Context context, final Intent intent) {
- final ConnectivityManager connMgr = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (OsUtil.isAtLeastJB_MR1()) {
- return intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, TYPE_NONE);
- } else {
- final NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
- ConnectivityManager.EXTRA_NETWORK_INFO);
- if (info != null) {
- return info.getType();
- }
- }
- return TYPE_NONE;
- }
-
- /**
- * Dump the raw MMS data into a file
- *
- * @param rawPdu The raw pdu data
- * @param pdu The parsed pdu, used to construct a dump file name
- */
- public static void dumpPdu(final byte[] rawPdu, final GenericPdu pdu) {
- if (rawPdu == null || rawPdu.length < 1) {
- return;
- }
- final String dumpFileName = MmsUtils.MMS_DUMP_PREFIX + getDumpFileId(pdu);
- final File dumpFile = DebugUtils.getDebugFile(dumpFileName, true);
- if (dumpFile != null) {
- try {
- final FileOutputStream fos = new FileOutputStream(dumpFile);
- final BufferedOutputStream bos = new BufferedOutputStream(fos);
- try {
- bos.write(rawPdu);
- bos.flush();
- } finally {
- bos.close();
- }
- DebugUtils.ensureReadable(dumpFile);
- } catch (final IOException e) {
- LogUtil.e(TAG, "dumpPdu: " + e, e);
- }
- }
- }
-
- /**
- * Get the dump file id based on the parsed PDU
- * 1. Use message id if not empty
- * 2. Use transaction id if message id is empty
- * 3. If all above is empty, use random UUID
- *
- * @param pdu the parsed PDU
- * @return the id of the dump file
- */
- private static String getDumpFileId(final GenericPdu pdu) {
- String fileId = null;
- if (pdu != null && pdu instanceof RetrieveConf) {
- final RetrieveConf retrieveConf = (RetrieveConf) pdu;
- if (retrieveConf.getMessageId() != null) {
- fileId = new String(retrieveConf.getMessageId());
- } else if (retrieveConf.getTransactionId() != null) {
- fileId = new String(retrieveConf.getTransactionId());
- }
- }
- if (TextUtils.isEmpty(fileId)) {
- fileId = UUID.randomUUID().toString();
- }
- return fileId;
- }
-}
diff --git a/src/com/android/messaging/sms/SmsException.java b/src/com/android/messaging/sms/SmsException.java
deleted file mode 100644
index 728db8c..0000000
--- a/src/com/android/messaging/sms/SmsException.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-/**
- * A generic Exception for errors in sending SMS
- */
-class SmsException extends Exception {
- private static final long serialVersionUID = 1L;
-
- /**
- * Creates a new SmsException.
- */
- public SmsException() {
- super();
- }
-
- /**
- * Creates a new SmsException with the specified detail message.
- *
- * @param message the detail message.
- */
- public SmsException(String message) {
- super(message);
- }
-
- /**
- * Creates a new SmsException with the specified cause.
- *
- * @param cause the cause.
- */
- public SmsException(Throwable cause) {
- super(cause);
- }
-
- /**
- * Creates a new SmsException with the specified detail message and cause.
- *
- * @param message the detail message.
- * @param cause the cause.
- */
- public SmsException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/com/android/messaging/sms/SmsReleaseStorage.java b/src/com/android/messaging/sms/SmsReleaseStorage.java
deleted file mode 100644
index 13a6284..0000000
--- a/src/com/android/messaging/sms/SmsReleaseStorage.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.res.Resources;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Class handling message cleanup when storage is low
- */
-public class SmsReleaseStorage {
- /**
- * Class representing a time duration specified by Gservices
- */
- public static class Duration {
- // Time duration unit types
- public static final int UNIT_WEEK = 'w';
- public static final int UNIT_MONTH = 'm';
- public static final int UNIT_YEAR = 'y';
-
- // Number of units
- public final int mCount;
- // Unit type: week, month or year
- public final int mUnit;
-
- public Duration(final int count, final int unit) {
- mCount = count;
- mUnit = unit;
- }
- }
-
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final Duration DEFAULT_DURATION = new Duration(1, Duration.UNIT_MONTH);
-
- private static final Pattern DURATION_PATTERN = Pattern.compile("([1-9]+\\d*)(w|m|y)");
- /**
- * Parse message retaining time duration specified by Gservices
- *
- * @return The parsed time duration from Gservices
- */
- public static Duration parseMessageRetainingDuration() {
- final String smsAutoDeleteMessageRetainingDuration =
- BugleGservices.get().getString(
- BugleGservicesKeys.SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION,
- BugleGservicesKeys.SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION_DEFAULT);
- final Matcher matcher = DURATION_PATTERN.matcher(smsAutoDeleteMessageRetainingDuration);
- try {
- if (matcher.matches()) {
- return new Duration(
- Integer.parseInt(matcher.group(1)),
- matcher.group(2).charAt(0));
- }
- } catch (final NumberFormatException e) {
- // Nothing to do
- }
- LogUtil.e(TAG, "SmsAutoDelete: invalid duration " +
- smsAutoDeleteMessageRetainingDuration);
- return DEFAULT_DURATION;
- }
-
- /**
- * Get string representation of the time duration
- *
- * @param duration
- * @return
- */
- public static String getMessageRetainingDurationString(final Duration duration) {
- final Resources resources = Factory.get().getApplicationContext().getResources();
- switch (duration.mUnit) {
- case Duration.UNIT_WEEK:
- return resources.getQuantityString(
- R.plurals.week_count, duration.mCount, duration.mCount);
- case Duration.UNIT_MONTH:
- return resources.getQuantityString(
- R.plurals.month_count, duration.mCount, duration.mCount);
- case Duration.UNIT_YEAR:
- return resources.getQuantityString(
- R.plurals.year_count, duration.mCount, duration.mCount);
- }
- throw new IllegalArgumentException(
- "SmsAutoDelete: invalid duration unit " + duration.mUnit);
- }
-
- // Time conversations
- private static final long WEEK_IN_MILLIS = 7 * 24 * 3600 * 1000L;
- private static final long MONTH_IN_MILLIS = 30 * 24 * 3600 * 1000L;
- private static final long YEAR_IN_MILLIS = 365 * 24 * 3600 * 1000L;
-
- /**
- * Convert time duration to time in milliseconds
- *
- * @param duration
- * @return
- */
- public static long durationToTimeInMillis(final Duration duration) {
- switch (duration.mUnit) {
- case Duration.UNIT_WEEK:
- return duration.mCount * WEEK_IN_MILLIS;
- case Duration.UNIT_MONTH:
- return duration.mCount * MONTH_IN_MILLIS;
- case Duration.UNIT_YEAR:
- return duration.mCount * YEAR_IN_MILLIS;
- }
- return -1L;
- }
-
- /**
- * Delete message actions:
- * 0: delete media messages
- * 1: delete old messages
- *
- * @param actionIndex The index of the delete action to perform
- * @param durationInMillis The time duration for retaining messages
- */
- public static void deleteMessages(final int actionIndex, final long durationInMillis) {
- int deleted = 0;
- switch (actionIndex) {
- case 0: {
- // Delete media
- deleted = MmsUtils.deleteMediaMessages();
- break;
- }
- case 1: {
- // Delete old messages
- final long now = System.currentTimeMillis();
- final long cutOffTimestampInMillis = now - durationInMillis;
- // Delete messages from telephony provider
- deleted = MmsUtils.deleteMessagesOlderThan(cutOffTimestampInMillis);
- break;
- }
- default: {
- LogUtil.e(TAG, "SmsStorageStatusManager: invalid action " + actionIndex);
- break;
- }
- }
-
- if (deleted > 0) {
- // Kick off a sync to update local db.
- SyncManager.sync();
- }
- }
-}
diff --git a/src/com/android/messaging/sms/SmsSender.java b/src/com/android/messaging/sms/SmsSender.java
deleted file mode 100644
index 889973f..0000000
--- a/src/com/android/messaging/sms/SmsSender.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SmsManager;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.receiver.SendStatusReceiver;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.Random;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Class that sends chat message via SMS.
- *
- * The interface emulates a blocking sending similar to making an HTTP request.
- * It calls the SmsManager to send a (potentially multipart) message and waits
- * on the sent status on each part. The waiting has a timeout so it won't wait
- * forever. Once the sent status of all parts received, the call returns.
- * A successful sending requires success status for all parts. Otherwise, we
- * pick the highest level of failure as the error for the whole message, which
- * is used to determine if we need to retry the sending.
- */
-public class SmsSender {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- public static final String EXTRA_PART_ID = "part_id";
-
- /*
- * A map for pending sms messages. The key is the random request UUID.
- */
- private static ConcurrentHashMap<Uri, SendResult> sPendingMessageMap =
- new ConcurrentHashMap<Uri, SendResult>();
-
- private static final Random RANDOM = new Random();
-
- // Whether we should send multipart SMS as separate messages
- private static Boolean sSendMultipartSmsAsSeparateMessages = null;
-
- /**
- * Class that holds the sent status for all parts of a multipart message sending
- */
- public static class SendResult {
- // Failure levels, used by the caller of the sender.
- // For temporary failures, possibly we could retry the sending
- // For permanent failures, we probably won't retry
- public static final int FAILURE_LEVEL_NONE = 0;
- public static final int FAILURE_LEVEL_TEMPORARY = 1;
- public static final int FAILURE_LEVEL_PERMANENT = 2;
-
- // Tracking the remaining pending parts in sending
- private int mPendingParts;
- // Tracking the highest level of failure among all parts
- private int mHighestFailureLevel;
-
- public SendResult(final int numOfParts) {
- Assert.isTrue(numOfParts > 0);
- mPendingParts = numOfParts;
- mHighestFailureLevel = FAILURE_LEVEL_NONE;
- }
-
- // Update the sent status of one part
- public void setPartResult(final int resultCode) {
- mPendingParts--;
- setHighestFailureLevel(resultCode);
- }
-
- public boolean hasPending() {
- return mPendingParts > 0;
- }
-
- public int getHighestFailureLevel() {
- return mHighestFailureLevel;
- }
-
- private int getFailureLevel(final int resultCode) {
- switch (resultCode) {
- case Activity.RESULT_OK:
- return FAILURE_LEVEL_NONE;
- case SmsManager.RESULT_ERROR_NO_SERVICE:
- return FAILURE_LEVEL_TEMPORARY;
- case SmsManager.RESULT_ERROR_RADIO_OFF:
- return FAILURE_LEVEL_PERMANENT;
- case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
- return FAILURE_LEVEL_PERMANENT;
- default: {
- LogUtil.e(TAG, "SmsSender: Unexpected sent intent resultCode = " + resultCode);
- return FAILURE_LEVEL_PERMANENT;
- }
- }
- }
-
- private void setHighestFailureLevel(final int resultCode) {
- final int level = getFailureLevel(resultCode);
- if (level > mHighestFailureLevel) {
- mHighestFailureLevel = level;
- }
- }
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("SendResult:");
- sb.append("Pending=").append(mPendingParts).append(",");
- sb.append("HighestFailureLevel=").append(mHighestFailureLevel);
- return sb.toString();
- }
- }
-
- public static void setResult(final Uri requestId, final int resultCode,
- final int errorCode, final int partId, int subId) {
- if (resultCode != Activity.RESULT_OK) {
- LogUtil.e(TAG, "SmsSender: failure in sending message part. "
- + " requestId=" + requestId + " partId=" + partId
- + " resultCode=" + resultCode + " errorCode=" + errorCode);
- if (errorCode != SendStatusReceiver.NO_ERROR_CODE) {
- final Context context = Factory.get().getApplicationContext();
- UiUtils.showToastAtBottom(getSendErrorToastMessage(context, subId, errorCode));
- }
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SmsSender: received sent result. " + " requestId=" + requestId
- + " partId=" + partId + " resultCode=" + resultCode);
- }
- }
- if (requestId != null) {
- final SendResult result = sPendingMessageMap.get(requestId);
- if (result != null) {
- synchronized (result) {
- result.setPartResult(resultCode);
- if (!result.hasPending()) {
- result.notifyAll();
- }
- }
- } else {
- LogUtil.e(TAG, "SmsSender: ignoring sent result. " + " requestId=" + requestId
- + " partId=" + partId + " resultCode=" + resultCode);
- }
- }
- }
-
- private static String getSendErrorToastMessage(final Context context, final int subId,
- final int errorCode) {
- final String carrierName = PhoneUtils.get(subId).getCarrierName();
- if (TextUtils.isEmpty(carrierName)) {
- return context.getString(R.string.carrier_send_error_unknown_carrier, errorCode);
- } else {
- return context.getString(R.string.carrier_send_error, carrierName, errorCode);
- }
- }
-
- // This should be called from a RequestWriter queue thread
- public static SendResult sendMessage(final Context context, final int subId, String dest,
- String message, final String serviceCenter, final boolean requireDeliveryReport,
- final Uri messageUri) throws SmsException {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SmsSender: sending message. " +
- "dest=" + dest + " message=" + message +
- " serviceCenter=" + serviceCenter +
- " requireDeliveryReport=" + requireDeliveryReport +
- " requestId=" + messageUri);
- }
- if (TextUtils.isEmpty(message)) {
- throw new SmsException("SmsSender: empty text message");
- }
- // Get the real dest and message for email or alias if dest is email or alias
- // Or sanitize the dest if dest is a number
- if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway()) &&
- (MmsSmsUtils.isEmailAddress(dest) || MmsSmsUtils.isAlias(dest, subId))) {
- // The original destination (email address) goes with the message
- message = dest + " " + message;
- // the new address is the email gateway #
- dest = MmsConfig.get(subId).getEmailGateway();
- } else {
- // remove spaces and dashes from destination number
- // (e.g. "801 555 1212" -> "8015551212")
- // (e.g. "+8211-123-4567" -> "+82111234567")
- dest = PhoneNumberUtils.stripSeparators(dest);
- }
- if (TextUtils.isEmpty(dest)) {
- throw new SmsException("SmsSender: empty destination address");
- }
- // Divide the input message by SMS length limit
- final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
- final ArrayList<String> messages = smsManager.divideMessage(message);
- if (messages == null || messages.size() < 1) {
- throw new SmsException("SmsSender: fails to divide message");
- }
- // Prepare the send result, which collects the send status for each part
- final SendResult pendingResult = new SendResult(messages.size());
- sPendingMessageMap.put(messageUri, pendingResult);
- // Actually send the sms
- sendInternal(
- context, subId, dest, messages, serviceCenter, requireDeliveryReport, messageUri);
- // Wait for pending intent to come back
- synchronized (pendingResult) {
- final long smsSendTimeoutInMillis = BugleGservices.get().getLong(
- BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS,
- BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS_DEFAULT);
- final long beginTime = SystemClock.elapsedRealtime();
- long waitTime = smsSendTimeoutInMillis;
- // We could possibly be woken up while still pending
- // so make sure we wait the full timeout period unless
- // we have the send results of all parts.
- while (pendingResult.hasPending() && waitTime > 0) {
- try {
- pendingResult.wait(waitTime);
- } catch (final InterruptedException e) {
- LogUtil.e(TAG, "SmsSender: sending wait interrupted");
- }
- waitTime = smsSendTimeoutInMillis - (SystemClock.elapsedRealtime() - beginTime);
- }
- }
- // Either we timed out or have all the results (success or failure)
- sPendingMessageMap.remove(messageUri);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SmsSender: sending completed. " +
- "dest=" + dest + " message=" + message + " result=" + pendingResult);
- }
- return pendingResult;
- }
-
- // Actually sending the message using SmsManager
- private static void sendInternal(final Context context, final int subId, String dest,
- final ArrayList<String> messages, final String serviceCenter,
- final boolean requireDeliveryReport, final Uri messageUri) throws SmsException {
- Assert.notNull(context);
- final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
- final int messageCount = messages.size();
- final ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount);
- final ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
- for (int i = 0; i < messageCount; i++) {
- // Make pending intents different for each message part
- final int partId = (messageCount <= 1 ? 0 : i + 1);
- if (requireDeliveryReport && (i == (messageCount - 1))) {
- // TODO we only care about the delivery status of the last part
- // Shall we have better tracking of delivery status of all parts?
- deliveryIntents.add(PendingIntent.getBroadcast(
- context,
- partId,
- getSendStatusIntent(context, SendStatusReceiver.MESSAGE_DELIVERED_ACTION,
- messageUri, partId, subId),
- 0/*flag*/));
- } else {
- deliveryIntents.add(null);
- }
- sentIntents.add(PendingIntent.getBroadcast(
- context,
- partId,
- getSendStatusIntent(context, SendStatusReceiver.MESSAGE_SENT_ACTION,
- messageUri, partId, subId),
- 0/*flag*/));
- }
- if (sSendMultipartSmsAsSeparateMessages == null) {
- sSendMultipartSmsAsSeparateMessages = MmsConfig.get(subId)
- .getSendMultipartSmsAsSeparateMessages();
- }
- try {
- if (sSendMultipartSmsAsSeparateMessages) {
- // If multipart sms is not supported, send them as separate messages
- for (int i = 0; i < messageCount; i++) {
- smsManager.sendTextMessage(dest,
- serviceCenter,
- messages.get(i),
- sentIntents.get(i),
- deliveryIntents.get(i));
- }
- } else {
- smsManager.sendMultipartTextMessage(
- dest, serviceCenter, messages, sentIntents, deliveryIntents);
- }
- } catch (final Exception e) {
- throw new SmsException("SmsSender: caught exception in sending " + e);
- }
- }
-
- private static Intent getSendStatusIntent(final Context context, final String action,
- final Uri requestUri, final int partId, final int subId) {
- // Encode requestId in intent data
- final Intent intent = new Intent(action, requestUri, context, SendStatusReceiver.class);
- intent.putExtra(SendStatusReceiver.EXTRA_PART_ID, partId);
- intent.putExtra(SendStatusReceiver.EXTRA_SUB_ID, subId);
- return intent;
- }
-}
diff --git a/src/com/android/messaging/sms/SmsStorageStatusManager.java b/src/com/android/messaging/sms/SmsStorageStatusManager.java
deleted file mode 100644
index ff7b79d..0000000
--- a/src/com/android/messaging/sms/SmsStorageStatusManager.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.sms;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.res.Resources;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.PendingIntentConstants;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Class that handles SMS auto delete and notification when storage is low
- */
-public class SmsStorageStatusManager {
- /**
- * Handles storage low signal for SMS
- */
- public static void handleStorageLow() {
- if (!PhoneUtils.getDefault().isSmsEnabled()) {
- return;
- }
-
- // TODO: Auto-delete messages, when that setting exists and is enabled
-
- // Notify low storage for SMS
- postStorageLowNotification();
- }
-
- /**
- * Handles storage OK signal for SMS
- */
- public static void handleStorageOk() {
- if (!PhoneUtils.getDefault().isSmsEnabled()) {
- return;
- }
- cancelStorageLowNotification();
- }
-
- /**
- * Post sms storage low notification
- */
- private static void postStorageLowNotification() {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final PendingIntent pendingIntent = UIIntents.get()
- .getPendingIntentForLowStorageNotifications(context);
-
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
- builder.setContentTitle(resources.getString(R.string.sms_storage_low_title))
- .setTicker(resources.getString(R.string.sms_storage_low_notification_ticker))
- .setSmallIcon(R.drawable.ic_failed_light)
- .setPriority(Notification.PRIORITY_DEFAULT)
- .setOngoing(true) // Can't be swiped off
- .setAutoCancel(false) // Don't auto cancel
- .setContentIntent(pendingIntent);
-
- final NotificationCompat.BigTextStyle bigTextStyle =
- new NotificationCompat.BigTextStyle(builder);
- bigTextStyle.bigText(resources.getString(R.string.sms_storage_low_text));
- final Notification notification = bigTextStyle.build();
-
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(Factory.get().getApplicationContext());
-
- notificationManager.notify(getNotificationTag(),
- PendingIntentConstants.SMS_STORAGE_LOW_NOTIFICATION_ID, notification);
- }
-
- /**
- * Cancel the notification
- */
- public static void cancelStorageLowNotification() {
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(Factory.get().getApplicationContext());
- notificationManager.cancel(getNotificationTag(),
- PendingIntentConstants.SMS_STORAGE_LOW_NOTIFICATION_ID);
- }
-
- private static String getNotificationTag() {
- return Factory.get().getApplicationContext().getPackageName() + ":smsstoragelow";
- }
-}
diff --git a/src/com/android/messaging/sms/SystemProperties.java b/src/com/android/messaging/sms/SystemProperties.java
deleted file mode 100644
index 669e448..0000000
--- a/src/com/android/messaging/sms/SystemProperties.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.sms;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Hacky way to call the hidden SystemProperties class API
- */
-class SystemProperties {
- private static Method sSystemPropertiesGetMethod = null;
-
- public static String get(final String name) {
- if (sSystemPropertiesGetMethod == null) {
- try {
- final Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
- if (systemPropertiesClass != null) {
- sSystemPropertiesGetMethod =
- systemPropertiesClass.getMethod("get", String.class);
- }
- } catch (final ClassNotFoundException e) {
- // Nothing to do
- } catch (final NoSuchMethodException e) {
- // Nothing to do
- }
- }
- if (sSystemPropertiesGetMethod != null) {
- try {
- return (String) sSystemPropertiesGetMethod.invoke(null, name);
- } catch (final IllegalArgumentException e) {
- // Nothing to do
- } catch (final IllegalAccessException e) {
- // Nothing to do
- } catch (final InvocationTargetException e) {
- // Nothing to do
- }
- }
- return null;
- }
-}
diff --git a/src/com/android/messaging/ui/AsyncImageView.java b/src/com/android/messaging/ui/AsyncImageView.java
deleted file mode 100644
index 9aaf0b1..0000000
--- a/src/com/android/messaging/ui/AsyncImageView.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.Nullable;
-import android.support.rastermill.FrameSequenceDrawable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.media.BindableMediaRequest;
-import com.android.messaging.datamodel.media.GifImageResource;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.HashSet;
-
-/**
- * An ImageView used to asynchronously request an image from MediaResourceManager and render it.
- */
-public class AsyncImageView extends ImageView implements MediaResourceLoadListener<ImageResource> {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- // 100ms delay before disposing the image in case the AsyncImageView is re-added to the UI
- private static final int DISPOSE_IMAGE_DELAY = 100;
-
- // AsyncImageView has a 1-1 binding relationship with an ImageRequest instance that requests
- // the image from the MediaResourceManager. Since the request is done asynchronously, we
- // want to make sure the image view is always bound to the latest image request that it
- // issues, so that when the image is loaded, the ImageRequest (which extends BindableData)
- // will be able to figure out whether the binding is still valid and whether the loaded image
- // should be delivered to the AsyncImageView via onMediaResourceLoaded() callback.
- @VisibleForTesting
- public final Binding<BindableMediaRequest<ImageResource>> mImageRequestBinding;
-
- /** True if we want the image to fade in when it loads */
- private boolean mFadeIn;
-
- /** True if we want the image to reveal (scale) when it loads. When set to true, this
- * will take precedence over {@link #mFadeIn} */
- private final boolean mReveal;
-
- // The corner radius for drawing rounded corners around bitmap. The default value is zero
- // (no rounded corners)
- private final int mCornerRadius;
- private final Path mRoundedCornerClipPath;
- private int mClipPathWidth;
- private int mClipPathHeight;
-
- // A placeholder drawable that takes the spot of the image when it's loading. The default
- // setting is null (no placeholder).
- private final Drawable mPlaceholderDrawable;
- protected ImageResource mImageResource;
- private final Runnable mDisposeRunnable = new Runnable() {
- @Override
- public void run() {
- if (mImageRequestBinding.isBound()) {
- mDetachedRequestDescriptor = (ImageRequestDescriptor)
- mImageRequestBinding.getData().getDescriptor();
- }
- unbindView();
- releaseImageResource();
- }
- };
-
- private AsyncImageViewDelayLoader mDelayLoader;
- private ImageRequestDescriptor mDetachedRequestDescriptor;
-
- public AsyncImageView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mImageRequestBinding = BindingBase.createBinding(this);
- final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.AsyncImageView,
- 0, 0);
- mFadeIn = attr.getBoolean(R.styleable.AsyncImageView_fadeIn, true);
- mReveal = attr.getBoolean(R.styleable.AsyncImageView_reveal, false);
- mPlaceholderDrawable = attr.getDrawable(R.styleable.AsyncImageView_placeholderDrawable);
- mCornerRadius = attr.getDimensionPixelSize(R.styleable.AsyncImageView_cornerRadius, 0);
- mRoundedCornerClipPath = new Path();
-
- attr.recycle();
- }
-
- /**
- * The main entrypoint for AsyncImageView to load image resource given an ImageRequestDescriptor
- * @param descriptor the request descriptor, or null if no image should be displayed
- */
- public void setImageResourceId(@Nullable final ImageRequestDescriptor descriptor) {
- final String requestKey = (descriptor == null) ? null : descriptor.getKey();
- if (mImageRequestBinding.isBound()) {
- if (TextUtils.equals(mImageRequestBinding.getData().getKey(), requestKey)) {
- // Don't re-request the bitmap if the new request is for the same resource.
- return;
- }
- unbindView();
- }
- setImage(null);
- resetTransientViewStates();
- if (!TextUtils.isEmpty(requestKey)) {
- maybeSetupPlaceholderDrawable(descriptor);
- final BindableMediaRequest<ImageResource> imageRequest =
- descriptor.buildAsyncMediaRequest(getContext(), this);
- requestImage(imageRequest);
- }
- }
-
- /**
- * Sets a delay loader that centrally manages image request delay loading logic.
- */
- public void setDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
- Assert.isTrue(mDelayLoader == null);
- mDelayLoader = delayLoader;
- }
-
- /**
- * Called by the delay loader when we can resume image loading.
- */
- public void resumeLoading() {
- Assert.notNull(mDelayLoader);
- Assert.isTrue(mImageRequestBinding.isBound());
- MediaResourceManager.get().requestMediaResourceAsync(mImageRequestBinding.getData());
- }
-
- /**
- * Setup the placeholder drawable if:
- * 1. There's an image to be loaded AND
- * 2. We are given a placeholder drawable AND
- * 3. The descriptor provided us with source width and height.
- */
- private void maybeSetupPlaceholderDrawable(final ImageRequestDescriptor descriptor) {
- if (!TextUtils.isEmpty(descriptor.getKey()) && mPlaceholderDrawable != null) {
- if (descriptor.sourceWidth != ImageRequest.UNSPECIFIED_SIZE &&
- descriptor.sourceHeight != ImageRequest.UNSPECIFIED_SIZE) {
- // Set a transparent inset drawable to the foreground so it will mimick the final
- // size of the image, and use the background to show the actual placeholder
- // drawable.
- setImageDrawable(PlaceholderInsetDrawable.fromDrawable(
- new ColorDrawable(Color.TRANSPARENT),
- descriptor.sourceWidth, descriptor.sourceHeight));
- }
- setBackground(mPlaceholderDrawable);
- }
- }
-
- protected void setImage(final ImageResource resource) {
- setImage(resource, false /* isCached */);
- }
-
- protected void setImage(final ImageResource resource, final boolean isCached) {
- // Switch reference to the new ImageResource. Make sure we release the current
- // resource and addRef() on the new resource so that the underlying bitmaps don't
- // get leaked or get recycled by the bitmap cache.
- releaseImageResource();
- // Ensure that any pending dispose runnables get removed.
- ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable);
- // The drawable may require work to get if its a static object so try to only make this call
- // once.
- final Drawable drawable = (resource != null) ? resource.getDrawable(getResources()) : null;
- if (drawable != null) {
- mImageResource = resource;
- mImageResource.addRef();
- setImageDrawable(drawable);
- if (drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).start();
- }
-
- if (getVisibility() == VISIBLE) {
- if (mReveal) {
- setVisibility(INVISIBLE);
- UiUtils.revealOrHideViewWithAnimation(this, VISIBLE, null);
- } else if (mFadeIn && !isCached) {
- // Hide initially to avoid flash.
- setAlpha(0F);
- animate().alpha(1F).start();
- }
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- if (mImageResource instanceof GifImageResource) {
- LogUtil.v(TAG, "setImage size unknown -- it's a GIF");
- } else {
- LogUtil.v(TAG, "setImage size: " + mImageResource.getMediaSize() +
- " width: " + mImageResource.getBitmap().getWidth() +
- " heigh: " + mImageResource.getBitmap().getHeight());
- }
- }
- }
- invalidate();
- }
-
- private void requestImage(final BindableMediaRequest<ImageResource> request) {
- mImageRequestBinding.bind(request);
- if (mDelayLoader == null || !mDelayLoader.isDelayLoadingImage()) {
- MediaResourceManager.get().requestMediaResourceAsync(request);
- } else {
- mDelayLoader.registerView(this);
- }
- }
-
- @Override
- public void onMediaResourceLoaded(final MediaRequest<ImageResource> request,
- final ImageResource resource, final boolean isCached) {
- if (mImageResource != resource) {
- setImage(resource, isCached);
- }
- }
-
- @Override
- public void onMediaResourceLoadError(
- final MediaRequest<ImageResource> request, final Exception exception) {
- // Media load failed, unbind and reset bitmap to default.
- unbindView();
- setImage(null);
- }
-
- private void releaseImageResource() {
- final Drawable drawable = getDrawable();
- if (drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).stop();
- ((FrameSequenceDrawable) drawable).destroy();
- }
- if (mImageResource != null) {
- mImageResource.release();
- mImageResource = null;
- }
- setImageDrawable(null);
- setBackground(null);
- }
-
- /**
- * Resets transient view states (eg. alpha, animations) before rebinding/reusing the view.
- */
- private void resetTransientViewStates() {
- clearAnimation();
- setAlpha(1F);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- // If it was recently removed, then cancel disposing, we're still using it.
- ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable);
-
- // When the image view gets detached and immediately re-attached, any fade-in animation
- // will be terminated, leaving the view in a semi-transparent state. Make sure we restore
- // alpha when the view is re-attached.
- if (mFadeIn) {
- setAlpha(1F);
- }
-
- // Check whether we are in a simple reuse scenario: detached from window, and reattached
- // later without rebinding. This may be done by containers such as the RecyclerView to
- // reuse the views. In this case, we would like to rebind the original image request.
- if (!mImageRequestBinding.isBound() && mDetachedRequestDescriptor != null) {
- setImageResourceId(mDetachedRequestDescriptor);
- }
- mDetachedRequestDescriptor = null;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- // Dispose the bitmap, but if an AysncImageView is removed from the window, then quickly
- // re-added, we shouldn't dispose, so wait a short time before disposing
- ThreadUtil.getMainThreadHandler().postDelayed(mDisposeRunnable, DISPOSE_IMAGE_DELAY);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- // The base implementation does not honor the minimum sizes. We try to to honor it here.
-
- final int measuredWidth = getMeasuredWidth();
- final int measuredHeight = getMeasuredHeight();
- if (measuredWidth >= getMinimumWidth() || measuredHeight >= getMinimumHeight()) {
- // We are ok if either of the minimum sizes is honored. Note that satisfying both the
- // sizes may not be possible, depending on the aspect ratio of the image and whether
- // a maximum size has been specified. This implementation only tries to handle the case
- // where both the minimum sizes are not being satisfied.
- return;
- }
-
- if (!getAdjustViewBounds()) {
- // The base implementation is reasonable in this case. If the view bounds cannot be
- // changed, it is not possible to satisfy the minimum sizes anyway.
- return;
- }
-
- final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
- // The base implementation is reasonable in this case.
- return;
- }
-
- int width = measuredWidth;
- int height = measuredHeight;
- // Get the minimum sizes that will honor other constraints as well.
- final int minimumWidth = resolveSize(
- getMinimumWidth(), getMaxWidth(), widthMeasureSpec);
- final int minimumHeight = resolveSize(
- getMinimumHeight(), getMaxHeight(), heightMeasureSpec);
- final float aspectRatio = measuredWidth / (float) measuredHeight;
- if (aspectRatio == 0) {
- // If the image is (close to) infinitely high, there is not much we can do.
- return;
- }
-
- if (width < minimumWidth) {
- height = resolveSize((int) (minimumWidth / aspectRatio),
- getMaxHeight(), heightMeasureSpec);
- width = (int) (height * aspectRatio);
- }
-
- if (height < minimumHeight) {
- width = resolveSize((int) (minimumHeight * aspectRatio),
- getMaxWidth(), widthMeasureSpec);
- height = (int) (width / aspectRatio);
- }
-
- setMeasuredDimension(width, height);
- }
-
- private static int resolveSize(int desiredSize, int maxSize, int measureSpec) {
- final int specMode = MeasureSpec.getMode(measureSpec);
- final int specSize = MeasureSpec.getSize(measureSpec);
- switch(specMode) {
- case MeasureSpec.UNSPECIFIED:
- return Math.min(desiredSize, maxSize);
-
- case MeasureSpec.AT_MOST:
- return Math.min(Math.min(desiredSize, specSize), maxSize);
-
- default:
- Assert.fail("Unreachable");
- return specSize;
- }
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- if (mCornerRadius > 0) {
- final int currentWidth = this.getWidth();
- final int currentHeight = this.getHeight();
- if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) {
- final RectF rect = new RectF(0, 0, currentWidth, currentHeight);
- mRoundedCornerClipPath.reset();
- mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius,
- Path.Direction.CW);
- mClipPathWidth = currentWidth;
- mClipPathHeight = currentHeight;
- }
-
- final int saveCount = canvas.getSaveCount();
- canvas.save();
- canvas.clipPath(mRoundedCornerClipPath);
- super.onDraw(canvas);
- canvas.restoreToCount(saveCount);
- } else {
- super.onDraw(canvas);
- }
- }
-
- private void unbindView() {
- if (mImageRequestBinding.isBound()) {
- mImageRequestBinding.unbind();
- if (mDelayLoader != null) {
- mDelayLoader.unregisterView(this);
- }
- }
- }
-
- /**
- * As a performance optimization, the consumer of the AsyncImageView may opt to delay loading
- * the image when it's busy doing other things (such as when a list view is scrolling). In
- * order to do this, the consumer can create a new AsyncImageViewDelayLoader instance to be
- * shared among all relevant AsyncImageViews (through setDelayLoader() method), and call
- * onStartDelayLoading() and onStopDelayLoading() to start and stop delay loading, respectively.
- */
- public static class AsyncImageViewDelayLoader {
- private boolean mShouldDelayLoad;
- private final HashSet<AsyncImageView> mAttachedViews;
-
- public AsyncImageViewDelayLoader() {
- mAttachedViews = new HashSet<AsyncImageView>();
- }
-
- private void registerView(final AsyncImageView view) {
- mAttachedViews.add(view);
- }
-
- private void unregisterView(final AsyncImageView view) {
- mAttachedViews.remove(view);
- }
-
- public boolean isDelayLoadingImage() {
- return mShouldDelayLoad;
- }
-
- /**
- * Called by the consumer of this view to delay loading images
- */
- public void onDelayLoading() {
- // Don't need to explicitly tell the AsyncImageView to stop loading since
- // ImageRequests are not cancellable.
- mShouldDelayLoad = true;
- }
-
- /**
- * Called by the consumer of this view to resume loading images
- */
- public void onResumeLoading() {
- if (mShouldDelayLoad) {
- mShouldDelayLoad = false;
-
- // Notify all attached views to resume loading.
- for (final AsyncImageView view : mAttachedViews) {
- view.resumeLoading();
- }
- mAttachedViews.clear();
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/AttachmentPreview.java b/src/com/android/messaging/ui/AttachmentPreview.java
deleted file mode 100644
index 7eea14b..0000000
--- a/src/com/android/messaging/ui/AttachmentPreview.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.ScrollView;
-
-import com.android.messaging.R;
-import com.android.messaging.annotation.VisibleForAnimation;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
-import com.android.messaging.ui.animation.PopupTransitionAnimation;
-import com.android.messaging.ui.conversation.ComposeMessageView;
-import com.android.messaging.ui.conversation.ConversationFragment;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AttachmentPreview extends ScrollView implements OnAttachmentClickListener {
- private FrameLayout mAttachmentView;
- private ComposeMessageView mComposeMessageView;
- private ImageButton mCloseButton;
- private int mAnimatedHeight = -1;
- private Animator mCloseGapAnimator;
- private boolean mPendingFirstUpdate;
- private Handler mHandler;
- private Runnable mHideRunnable;
- private boolean mPendingHideCanceled;
-
- private static final int CLOSE_BUTTON_REVEAL_STAGGER_MILLIS = 300;
-
- public AttachmentPreview(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mHandler = new Handler(Looper.getMainLooper());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mCloseButton = (ImageButton) findViewById(R.id.close_button);
- mCloseButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- mComposeMessageView.clearAttachments();
- }
- });
-
- mAttachmentView = (FrameLayout) findViewById(R.id.attachment_view);
-
- // The attachment preview is a scroll view so that it can show the bottom portion of the
- // attachment whenever the space is tight (e.g. when in landscape mode). Per design
- // request we'd like to make the attachment view always scrolled to the bottom.
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- post(new Runnable() {
- @Override
- public void run() {
- final int childCount = getChildCount();
- if (childCount > 0) {
- final View lastChild = getChildAt(childCount - 1);
- scrollTo(getScrollX(), lastChild.getBottom() - getHeight());
- }
- }
- });
- }
- });
- mPendingFirstUpdate = true;
- }
-
- public void setComposeMessageView(final ComposeMessageView composeMessageView) {
- mComposeMessageView = composeMessageView;
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mAnimatedHeight >= 0) {
- setMeasuredDimension(getMeasuredWidth(), mAnimatedHeight);
- }
- }
-
- private void cancelPendingHide() {
- mPendingHideCanceled = true;
- }
-
- public void hideAttachmentPreview() {
- if (getVisibility() != GONE) {
- UiUtils.revealOrHideViewWithAnimation(mCloseButton, GONE,
- null /* onFinishRunnable */);
- startCloseGapAnimationOnAttachmentClear();
-
- if (mAttachmentView.getChildCount() > 0) {
- mPendingHideCanceled = false;
- final View viewToHide = mAttachmentView.getChildCount() > 1 ?
- mAttachmentView : mAttachmentView.getChildAt(0);
- UiUtils.revealOrHideViewWithAnimation(viewToHide, INVISIBLE,
- new Runnable() {
- @Override
- public void run() {
- // Only hide if we are didn't get overruled by showing
- if (!mPendingHideCanceled) {
- mAttachmentView.removeAllViews();
- setVisibility(GONE);
- }
- }
- });
- } else {
- mAttachmentView.removeAllViews();
- setVisibility(GONE);
- }
- }
- }
-
- // returns true if we have attachments
- public boolean onAttachmentsChanged(final DraftMessageData draftMessageData) {
- final boolean isFirstUpdate = mPendingFirstUpdate;
- final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
- final List<PendingAttachmentData> pendingAttachments =
- draftMessageData.getReadOnlyPendingAttachments();
-
- // Any change in attachments would invalidate the animated height animation.
- cancelCloseGapAnimation();
- mPendingFirstUpdate = false;
-
- final int combinedAttachmentCount = attachments.size() + pendingAttachments.size();
- mCloseButton.setContentDescription(getResources()
- .getQuantityString(R.plurals.attachment_preview_close_content_description,
- combinedAttachmentCount));
- if (combinedAttachmentCount == 0) {
- mHideRunnable = new Runnable() {
- @Override
- public void run() {
- mHideRunnable = null;
- // Only start the hiding if there are still no attachments
- if (attachments.size() + pendingAttachments.size() == 0) {
- hideAttachmentPreview();
- }
- }
- };
- if (draftMessageData.isSending()) {
- // Wait to hide until the message is ready to start animating
- // We'll execute immediately when the animation triggers
- mHandler.postDelayed(mHideRunnable,
- ConversationFragment.MESSAGE_ANIMATION_MAX_WAIT);
- } else {
- // Run immediately when clearing attachments
- mHideRunnable.run();
- }
- return false;
- }
-
- cancelPendingHide(); // We're showing
- if (getVisibility() != VISIBLE) {
- setVisibility(VISIBLE);
- mAttachmentView.setVisibility(VISIBLE);
-
- // Don't animate in the close button if this is the first update after view creation.
- // This is the initial draft load from database for pre-existing drafts.
- if (!isFirstUpdate) {
- // Reveal the close button after the view animates in.
- mCloseButton.setVisibility(INVISIBLE);
- ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- UiUtils.revealOrHideViewWithAnimation(mCloseButton, VISIBLE,
- null /* onFinishRunnable */);
- }
- }, UiUtils.MEDIAPICKER_TRANSITION_DURATION + CLOSE_BUTTON_REVEAL_STAGGER_MILLIS);
- }
- }
-
- // Merge the pending attachment list with real attachment. Design would prefer these be
- // in LIFO order user can see added images past the 5th one but we also want them to be in
- // order and we want it to be WYSIWYG.
- final List<MessagePartData> combinedAttachments = new ArrayList<>();
- combinedAttachments.addAll(attachments);
- combinedAttachments.addAll(pendingAttachments);
-
- final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
- if (combinedAttachmentCount > 1) {
- MultiAttachmentLayout multiAttachmentLayout = null;
- Rect transitionRect = null;
- if (mAttachmentView.getChildCount() > 0) {
- final View firstChild = mAttachmentView.getChildAt(0);
- if (firstChild instanceof MultiAttachmentLayout) {
- Assert.equals(1, mAttachmentView.getChildCount());
- multiAttachmentLayout = (MultiAttachmentLayout) firstChild;
- multiAttachmentLayout.bindAttachments(combinedAttachments,
- null /* transitionRect */, combinedAttachmentCount);
- } else {
- transitionRect = new Rect(firstChild.getLeft(), firstChild.getTop(),
- firstChild.getRight(), firstChild.getBottom());
- }
- }
- if (multiAttachmentLayout == null) {
- multiAttachmentLayout = AttachmentPreviewFactory.createMultiplePreview(
- getContext(), this);
- multiAttachmentLayout.bindAttachments(combinedAttachments, transitionRect,
- combinedAttachmentCount);
- mAttachmentView.removeAllViews();
- mAttachmentView.addView(multiAttachmentLayout);
- }
- } else {
- final MessagePartData attachment = combinedAttachments.get(0);
- boolean shouldAnimate = true;
- if (mAttachmentView.getChildCount() > 0) {
- // If we are going from N->1 attachments, try to use the current bounds
- // bounds as the starting rect.
- shouldAnimate = false;
- final View firstChild = mAttachmentView.getChildAt(0);
- if (firstChild instanceof MultiAttachmentLayout &&
- attachment instanceof MediaPickerMessagePartData) {
- final View leftoverView = ((MultiAttachmentLayout) firstChild)
- .findViewForAttachment(attachment);
- if (leftoverView != null) {
- final Rect currentRect = UiUtils.getMeasuredBoundsOnScreen(leftoverView);
- if (!currentRect.isEmpty() &&
- attachment instanceof MediaPickerMessagePartData) {
- ((MediaPickerMessagePartData) attachment).setStartRect(currentRect);
- shouldAnimate = true;
- }
- }
- }
- }
- mAttachmentView.removeAllViews();
- final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview(
- layoutInflater, attachment, mAttachmentView,
- AttachmentPreviewFactory.TYPE_SINGLE, true /* startImageRequest */, this);
- if (attachmentView != null) {
- mAttachmentView.addView(attachmentView);
- if (shouldAnimate) {
- tryAnimateViewIn(attachment, attachmentView);
- }
- }
- }
- return true;
- }
-
- public void onMessageAnimationStart() {
- if (mHideRunnable == null) {
- return;
- }
-
- // Run the hide animation at the same time as the message animation
- mHandler.removeCallbacks(mHideRunnable);
- setVisibility(View.INVISIBLE);
- mHideRunnable.run();
- }
-
- static void tryAnimateViewIn(final MessagePartData attachmentData, final View view) {
- if (attachmentData instanceof MediaPickerMessagePartData) {
- final Rect startRect = ((MediaPickerMessagePartData) attachmentData).getStartRect();
- new PopupTransitionAnimation(startRect, view).startAfterLayoutComplete();
- }
- }
-
- @VisibleForAnimation
- public void setAnimatedHeight(final int animatedHeight) {
- if (mAnimatedHeight != animatedHeight) {
- mAnimatedHeight = animatedHeight;
- requestLayout();
- }
- }
-
- /**
- * Kicks off an animation to animate the layout change for closing the gap between the
- * message list and the compose message box when the attachments are cleared.
- */
- private void startCloseGapAnimationOnAttachmentClear() {
- // Cancel existing animation.
- cancelCloseGapAnimation();
- mCloseGapAnimator = ObjectAnimator.ofInt(this, "animatedHeight", getHeight(), 0);
- mCloseGapAnimator.start();
- }
-
- private void cancelCloseGapAnimation() {
- if (mCloseGapAnimator != null) {
- mCloseGapAnimator.cancel();
- mCloseGapAnimator = null;
- }
- mAnimatedHeight = -1;
- }
-
- @Override
- public boolean onAttachmentClick(final MessagePartData attachment,
- final Rect viewBoundsOnScreen, final boolean longPress) {
- if (longPress) {
- mComposeMessageView.onAttachmentPreviewLongClicked();
- return true;
- }
-
- if (!(attachment instanceof PendingAttachmentData) && attachment.isImage()) {
- mComposeMessageView.displayPhoto(attachment.getContentUri(), viewBoundsOnScreen);
- return true;
- }
- return false;
- }
-}
diff --git a/src/com/android/messaging/ui/AttachmentPreviewFactory.java b/src/com/android/messaging/ui/AttachmentPreviewFactory.java
deleted file mode 100644
index ed5d4d7..0000000
--- a/src/com/android/messaging/ui/AttachmentPreviewFactory.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
-import android.view.ViewGroup;
-import android.widget.FrameLayout.LayoutParams;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.datamodel.data.VCardContactItemData;
-import com.android.messaging.datamodel.media.FileImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
-import com.android.messaging.ui.PersonItemView.PersonItemViewListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-/**
- * A view factory that creates previews for single/multiple attachments.
- */
-public class AttachmentPreviewFactory {
- /** Standalone attachment preview */
- public static final int TYPE_SINGLE = 1;
-
- /** Attachment preview displayed in a multi-attachment layout */
- public static final int TYPE_MULTIPLE = 2;
-
- /** Attachment preview displayed in the attachment chooser grid view */
- public static final int TYPE_CHOOSER_GRID = 3;
-
- public static View createAttachmentPreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType, final boolean startImageRequest,
- @Nullable final OnAttachmentClickListener clickListener) {
- final String contentType = attachmentData.getContentType();
- View attachmentView = null;
- if (attachmentData instanceof PendingAttachmentData) {
- attachmentView = createPendingAttachmentPreview(layoutInflater, parent,
- (PendingAttachmentData) attachmentData);
- } else if (ContentType.isImageType(contentType)) {
- attachmentView = createImagePreview(layoutInflater, attachmentData, parent, viewType,
- startImageRequest);
- } else if (ContentType.isAudioType(contentType)) {
- attachmentView = createAudioPreview(layoutInflater, attachmentData, parent, viewType);
- } else if (ContentType.isVideoType(contentType)) {
- attachmentView = createVideoPreview(layoutInflater, attachmentData, parent, viewType);
- } else if (ContentType.isVCardType(contentType)) {
- attachmentView = createVCardPreview(layoutInflater, attachmentData, parent, viewType);
- } else {
- Assert.fail("unsupported attachment type: " + contentType);
- return null;
- }
-
- // Some views have a caption, set the text/visibility if one exists
- final TextView captionView = (TextView) attachmentView.findViewById(R.id.caption);
- if (captionView != null) {
- final String caption = attachmentData.getText();
- captionView.setVisibility(TextUtils.isEmpty(caption) ? View.GONE : View.VISIBLE);
- captionView.setText(caption);
- }
-
- if (attachmentView != null && clickListener != null) {
- attachmentView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
- clickListener.onAttachmentClick(attachmentData, bounds,
- false /* longPress */);
- }
- });
- attachmentView.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View view) {
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
- return clickListener.onAttachmentClick(attachmentData, bounds,
- true /* longPress */);
- }
- });
- }
- return attachmentView;
- }
-
- public static MultiAttachmentLayout createMultiplePreview(final Context context,
- final OnAttachmentClickListener listener) {
- final MultiAttachmentLayout multiAttachmentLayout =
- new MultiAttachmentLayout(context, null);
- final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- multiAttachmentLayout.setLayoutParams(layoutParams);
- multiAttachmentLayout.setOnAttachmentClickListener(listener);
- return multiAttachmentLayout;
- }
-
- public static ImageRequestDescriptor getImageRequestDescriptorForAttachment(
- final MessagePartData attachmentData, final int desiredWidth, final int desiredHeight) {
- final Uri uri = attachmentData.getContentUri();
- final String contentType = attachmentData.getContentType();
- if (ContentType.isImageType(contentType)) {
- final String filePath = UriUtil.getFilePathFromUri(uri);
- if (filePath != null) {
- return new FileImageRequestDescriptor(filePath, desiredWidth, desiredHeight,
- attachmentData.getWidth(), attachmentData.getHeight(),
- false /* canUseThumbnail */, true /* allowCompression */,
- false /* isStatic */);
- } else {
- return new UriImageRequestDescriptor(uri, desiredWidth, desiredHeight,
- attachmentData.getWidth(), attachmentData.getHeight(),
- true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- }
- }
- return null;
- }
-
- private static View createImagePreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType, final boolean startImageRequest) {
- int layoutId = R.layout.attachment_single_image;
- switch (viewType) {
- case AttachmentPreviewFactory.TYPE_SINGLE:
- layoutId = R.layout.attachment_single_image;
- break;
- case AttachmentPreviewFactory.TYPE_MULTIPLE:
- layoutId = R.layout.attachment_multiple_image;
- break;
- case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
- layoutId = R.layout.attachment_chooser_image;
- break;
- default:
- Assert.fail("unsupported attachment view type!");
- break;
- }
- final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
- final AsyncImageView imageView = (AsyncImageView) view.findViewById(
- R.id.attachment_image_view);
- int maxWidth = imageView.getMaxWidth();
- int maxHeight = imageView.getMaxHeight();
- if (viewType == TYPE_CHOOSER_GRID) {
- final Resources resources = layoutInflater.getContext().getResources();
- maxWidth = maxHeight = resources.getDimensionPixelSize(
- R.dimen.attachment_grid_image_cell_size);
- }
- if (maxWidth <= 0 || maxWidth == Integer.MAX_VALUE) {
- maxWidth = ImageRequest.UNSPECIFIED_SIZE;
- }
- if (maxHeight <= 0 || maxHeight == Integer.MAX_VALUE) {
- maxHeight = ImageRequest.UNSPECIFIED_SIZE;
- }
- if (startImageRequest) {
- imageView.setImageResourceId(getImageRequestDescriptorForAttachment(attachmentData,
- maxWidth, maxHeight));
- }
- imageView.setContentDescription(
- parent.getResources().getString(R.string.message_image_content_description));
- return view;
- }
-
- private static View createPendingAttachmentPreview(final LayoutInflater layoutInflater,
- final ViewGroup parent, final PendingAttachmentData attachmentData) {
- final View pendingItemView = layoutInflater.inflate(R.layout.attachment_pending_item,
- parent, false);
- final ImageView imageView = (ImageView)
- pendingItemView.findViewById(R.id.pending_item_view);
- final ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
- final int defaultSize = layoutInflater.getContext().getResources().getDimensionPixelSize(
- R.dimen.pending_attachment_size);
- layoutParams.width = attachmentData.getWidth() == MessagePartData.UNSPECIFIED_SIZE ?
- defaultSize : attachmentData.getWidth();
- layoutParams.height = attachmentData.getHeight() == MessagePartData.UNSPECIFIED_SIZE ?
- defaultSize : attachmentData.getHeight();
- return pendingItemView;
- }
-
- private static View createVCardPreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType) {
- int layoutId = R.layout.attachment_single_vcard;
- switch (viewType) {
- case AttachmentPreviewFactory.TYPE_SINGLE:
- layoutId = R.layout.attachment_single_vcard;
- break;
- case AttachmentPreviewFactory.TYPE_MULTIPLE:
- layoutId = R.layout.attachment_multiple_vcard;
- break;
- case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
- layoutId = R.layout.attachment_chooser_vcard;
- break;
- default:
- Assert.fail("unsupported attachment view type!");
- break;
- }
- final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
- final PersonItemView vcardPreview = (PersonItemView) view.findViewById(
- R.id.vcard_attachment_view);
- vcardPreview.setAvatarOnly(viewType != AttachmentPreviewFactory.TYPE_SINGLE);
- vcardPreview.bind(DataModel.get().createVCardContactItemData(layoutInflater.getContext(),
- attachmentData));
- vcardPreview.setListener(new PersonItemViewListener() {
- @Override
- public void onPersonClicked(final PersonItemData data) {
- Assert.isTrue(data instanceof VCardContactItemData);
- final VCardContactItemData vCardData = (VCardContactItemData) data;
- if (vCardData.hasValidVCard()) {
- final Uri vCardUri = vCardData.getVCardUri();
- UIIntents.get().launchVCardDetailActivity(vcardPreview.getContext(), vCardUri);
- }
- }
-
- @Override
- public boolean onPersonLongClicked(final PersonItemData data) {
- return false;
- }
- });
- return view;
- }
-
- private static View createAudioPreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType) {
- int layoutId = R.layout.attachment_single_audio;
- switch (viewType) {
- case AttachmentPreviewFactory.TYPE_SINGLE:
- layoutId = R.layout.attachment_single_audio;
- break;
- case AttachmentPreviewFactory.TYPE_MULTIPLE:
- layoutId = R.layout.attachment_multiple_audio;
- break;
- case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
- layoutId = R.layout.attachment_chooser_audio;
- break;
- default:
- Assert.fail("unsupported attachment view type!");
- break;
- }
- final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
- final AudioAttachmentView audioView = (AudioAttachmentView)
- view.findViewById(R.id.audio_attachment_view);
- audioView.bindMessagePartData(attachmentData, false /* incoming */);
- return view;
- }
-
- private static View createVideoPreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType) {
- int layoutId = R.layout.attachment_single_video;
- switch (viewType) {
- case AttachmentPreviewFactory.TYPE_SINGLE:
- layoutId = R.layout.attachment_single_video;
- break;
- case AttachmentPreviewFactory.TYPE_MULTIPLE:
- layoutId = R.layout.attachment_multiple_video;
- break;
- case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
- layoutId = R.layout.attachment_chooser_video;
- break;
- default:
- Assert.fail("unsupported attachment view type!");
- break;
- }
- final VideoThumbnailView videoThumbnail = (VideoThumbnailView) layoutInflater.inflate(
- layoutId, parent, false /* attachToRoot */);
- videoThumbnail.setSource(attachmentData, false /* incomingMessage */);
- return videoThumbnail;
- }
-}
diff --git a/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java b/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java
deleted file mode 100644
index 724c4fe..0000000
--- a/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.ViewSwitcher;
-
-import com.android.messaging.R;
-
-/**
- * Shows a tinted play pause button.
- */
-public class AudioAttachmentPlayPauseButton extends ViewSwitcher {
- private ImageView mPlayButton;
- private ImageView mPauseButton;
-
- private boolean mShowAsIncoming;
-
- public AudioAttachmentPlayPauseButton(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mPlayButton = (ImageView) findViewById(R.id.play_button);
- mPauseButton = (ImageView) findViewById(R.id.pause_button);
- updateAppearance();
- }
-
- public void setVisualStyle(final boolean showAsIncoming) {
- if (mShowAsIncoming != showAsIncoming) {
- mShowAsIncoming = showAsIncoming;
- updateAppearance();
- }
- }
-
- private void updateAppearance() {
- mPlayButton.setImageDrawable(ConversationDrawables.get()
- .getPlayButtonDrawable(mShowAsIncoming));
- mPauseButton.setImageDrawable(ConversationDrawables.get()
- .getPauseButtonDrawable(mShowAsIncoming));
- }
-}
diff --git a/src/com/android/messaging/ui/AudioAttachmentView.java b/src/com/android/messaging/ui/AudioAttachmentView.java
deleted file mode 100644
index bb649b0..0000000
--- a/src/com/android/messaging/ui/AudioAttachmentView.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaPlayer.OnPreparedListener;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.ui.mediapicker.PausableChronometer;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * A reusable widget that hosts an audio player for audio attachment playback. This widget is used
- * by both the media picker and the conversation message view to show audio attachments.
- */
-public class AudioAttachmentView extends LinearLayout {
- /** The normal layout mode where we have the play button, timer and progress bar */
- private static final int LAYOUT_MODE_NORMAL = 0;
-
- /** The compact layout mode with only the play button and the timer beneath it. Suitable
- * for displaying in limited space such as multi-attachment layout */
- private static final int LAYOUT_MODE_COMPACT = 1;
-
- /** The sub-compact layout mode with only the play button. */
- private static final int LAYOUT_MODE_SUB_COMPACT = 2;
-
- private static final int PLAY_BUTTON = 0;
- private static final int PAUSE_BUTTON = 1;
-
- private AudioAttachmentPlayPauseButton mPlayPauseButton;
- private PausableChronometer mChronometer;
- private AudioPlaybackProgressBar mProgressBar;
- private MediaPlayer mMediaPlayer;
-
- private Uri mDataSourceUri;
-
- // The corner radius for drawing rounded corners. The default value is zero (no rounded corners)
- private final int mCornerRadius;
- private final Path mRoundedCornerClipPath;
- private int mClipPathWidth;
- private int mClipPathHeight;
-
- // Indicates whether the attachment view is to be styled as a part of an incoming message.
- private boolean mShowAsIncoming;
-
- private boolean mPrepared;
- private boolean mPlaybackFinished;
- private final int mMode;
-
- public AudioAttachmentView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- final TypedArray typedAttributes =
- context.obtainStyledAttributes(attrs, R.styleable.AudioAttachmentView);
- mMode = typedAttributes.getInt(R.styleable.AudioAttachmentView_layoutMode,
- LAYOUT_MODE_NORMAL);
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- inflater.inflate(R.layout.audio_attachment_view, this, true);
- typedAttributes.recycle();
-
- setWillNotDraw(mMode != LAYOUT_MODE_SUB_COMPACT);
- mRoundedCornerClipPath = new Path();
- mCornerRadius = context.getResources().getDimensionPixelSize(
- R.dimen.conversation_list_image_preview_corner_radius);
- setContentDescription(context.getString(R.string.audio_attachment_content_description));
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mPlayPauseButton = (AudioAttachmentPlayPauseButton) findViewById(R.id.play_pause_button);
- mChronometer = (PausableChronometer) findViewById(R.id.timer);
- mProgressBar = (AudioPlaybackProgressBar) findViewById(R.id.progress);
- mPlayPauseButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- setupMediaPlayer();
- if (mMediaPlayer != null && mPrepared) {
- if (mMediaPlayer.isPlaying()) {
- mMediaPlayer.pause();
- mChronometer.pause();
- mProgressBar.pause();
- } else {
- playAudio();
- }
- }
- updatePlayPauseButtonState();
- }
- });
- updatePlayPauseButtonState();
- initializeViewsForMode();
- }
-
- /**
- * Bind the audio attachment view with a MessagePartData.
- * @param incoming indicates whether the attachment view is to be styled as a part of an
- * incoming message.
- */
- public void bindMessagePartData(final MessagePartData messagePartData,
- final boolean incoming) {
- Assert.isTrue(messagePartData == null ||
- ContentType.isAudioType(messagePartData.getContentType()));
- final Uri contentUri = (messagePartData == null) ? null : messagePartData.getContentUri();
- bind(contentUri, incoming);
- }
-
- public void bind(final Uri dataSourceUri, final boolean incoming) {
- final String currentUriString = (mDataSourceUri == null) ? "" : mDataSourceUri.toString();
- final String newUriString = (dataSourceUri == null) ? "" : dataSourceUri.toString();
- mShowAsIncoming = incoming;
- if (!TextUtils.equals(currentUriString, newUriString)) {
- mDataSourceUri = dataSourceUri;
- resetToZeroState();
- }
- }
-
- private void playAudio() {
- Assert.notNull(mMediaPlayer);
- if (mPlaybackFinished) {
- mMediaPlayer.seekTo(0);
- mChronometer.restart();
- mProgressBar.restart();
- mPlaybackFinished = false;
- } else {
- mChronometer.resume();
- mProgressBar.resume();
- }
- mMediaPlayer.start();
- }
-
- private void onAudioReplayError(final int what, final int extra, final Exception exception) {
- if (exception == null) {
- LogUtil.e(LogUtil.BUGLE_TAG, "audio replay failed, what=" + what +
- ", extra=" + extra);
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG, "audio replay failed, exception=" + exception);
- }
- UiUtils.showToastAtBottom(R.string.audio_recording_replay_failed);
- releaseMediaPlayer();
- }
-
- private void setupMediaPlayer() {
- Assert.notNull(mDataSourceUri);
- if (mMediaPlayer == null) {
- Assert.isTrue(!mPrepared);
- mMediaPlayer = new MediaPlayer();
- try {
- mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mMediaPlayer.setDataSource(Factory.get().getApplicationContext(), mDataSourceUri);
- mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(final MediaPlayer mp) {
- updatePlayPauseButtonState();
- mChronometer.reset();
- mChronometer.setBase(SystemClock.elapsedRealtime() -
- mMediaPlayer.getDuration());
- mProgressBar.reset();
-
- mPlaybackFinished = true;
- }
- });
-
- mMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
- @Override
- public void onPrepared(final MediaPlayer mp) {
- // Set base on the chronometer so we can show the full length of the audio.
- mChronometer.setBase(SystemClock.elapsedRealtime() -
- mMediaPlayer.getDuration());
- mProgressBar.setDuration(mMediaPlayer.getDuration());
- mMediaPlayer.seekTo(0);
- mPrepared = true;
- }
- });
-
- mMediaPlayer.setOnErrorListener(new OnErrorListener() {
- @Override
- public boolean onError(final MediaPlayer mp, final int what, final int extra) {
- onAudioReplayError(what, extra, null);
- return true;
- }
- });
- mMediaPlayer.prepareAsync();
- } catch (final Exception exception) {
- onAudioReplayError(0, 0, exception);
- releaseMediaPlayer();
- }
- }
- }
-
- private void releaseMediaPlayer() {
- if (mMediaPlayer != null) {
- mMediaPlayer.release();
- mMediaPlayer = null;
- mPrepared = false;
- mPlaybackFinished = false;
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- releaseMediaPlayer();
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- if (mMode != LAYOUT_MODE_SUB_COMPACT) {
- return;
- }
-
- final int currentWidth = this.getWidth();
- final int currentHeight = this.getHeight();
- if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) {
- final RectF rect = new RectF(0, 0, currentWidth, currentHeight);
- mRoundedCornerClipPath.reset();
- mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius,
- Path.Direction.CW);
- mClipPathWidth = currentWidth;
- mClipPathHeight = currentHeight;
- }
-
- canvas.clipPath(mRoundedCornerClipPath);
- super.onDraw(canvas);
- }
-
- private void updatePlayPauseButtonState() {
- if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) {
- mPlayPauseButton.setDisplayedChild(PLAY_BUTTON);
- } else {
- mPlayPauseButton.setDisplayedChild(PAUSE_BUTTON);
- }
- }
-
- private void resetToZeroState() {
- // Release the media player so it may be set up with the new audio source.
- releaseMediaPlayer();
- mChronometer.reset();
- mProgressBar.reset();
- updateVisualStyle();
-
- if (mDataSourceUri != null) {
- // Re-ensure the media player, so we can read the duration of the audio.
- setupMediaPlayer();
- }
- }
-
- private void updateVisualStyle() {
- if (mMode == LAYOUT_MODE_SUB_COMPACT) {
- // Sub-compact mode has static visual appearance already set up during initialization.
- return;
- }
-
- if (mShowAsIncoming) {
- mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_incoming));
- } else {
- mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_outgoing));
- }
- mProgressBar.setVisualStyle(mShowAsIncoming);
- mPlayPauseButton.setVisualStyle(mShowAsIncoming);
- updatePlayPauseButtonState();
- }
-
- private void initializeViewsForMode() {
- switch (mMode) {
- case LAYOUT_MODE_NORMAL:
- setOrientation(HORIZONTAL);
- mProgressBar.setVisibility(VISIBLE);
- break;
-
- case LAYOUT_MODE_COMPACT:
- setOrientation(VERTICAL);
- mProgressBar.setVisibility(GONE);
- ((MarginLayoutParams) mPlayPauseButton.getLayoutParams()).setMargins(0, 0, 0, 0);
- ((MarginLayoutParams) mChronometer.getLayoutParams()).setMargins(0, 0, 0, 0);
- break;
-
- case LAYOUT_MODE_SUB_COMPACT:
- setOrientation(VERTICAL);
- mProgressBar.setVisibility(GONE);
- mChronometer.setVisibility(GONE);
- ((MarginLayoutParams) mPlayPauseButton.getLayoutParams()).setMargins(0, 0, 0, 0);
- final ImageView playButton = (ImageView) findViewById(R.id.play_button);
- playButton.setImageDrawable(
- getResources().getDrawable(R.drawable.ic_preview_play));
- final ImageView pauseButton = (ImageView) findViewById(R.id.pause_button);
- pauseButton.setImageDrawable(
- getResources().getDrawable(R.drawable.ic_preview_pause));
- break;
-
- default:
- Assert.fail("Unsupported mode for AudioAttachmentView!");
- break;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/AudioPlaybackProgressBar.java b/src/com/android/messaging/ui/AudioPlaybackProgressBar.java
deleted file mode 100644
index a5b3a57..0000000
--- a/src/com/android/messaging/ui/AudioPlaybackProgressBar.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.animation.ObjectAnimator;
-import android.animation.TimeAnimator;
-import android.animation.TimeAnimator.TimeListener;
-import android.content.Context;
-import android.graphics.drawable.ClipDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.widget.ProgressBar;
-
-/**
- * Shows a styled progress bar that is synchronized with the playback state of an audio attachment.
- */
-public class AudioPlaybackProgressBar extends ProgressBar implements PlaybackStateView {
- private long mDurationInMillis;
- private final TimeAnimator mUpdateAnimator;
- private long mCumulativeTime = 0;
- private long mCurrentPlayStartTime = 0;
- private boolean mIncoming = false;
-
- public AudioPlaybackProgressBar(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- mUpdateAnimator = new TimeAnimator();
- mUpdateAnimator.setRepeatCount(ObjectAnimator.INFINITE);
- mUpdateAnimator.setTimeListener(new TimeListener() {
- @Override
- public void onTimeUpdate(final TimeAnimator animation, final long totalTime,
- final long deltaTime) {
- int progress = 0;
- if (mDurationInMillis > 0) {
- progress = (int) (((mCumulativeTime + SystemClock.elapsedRealtime() -
- mCurrentPlayStartTime) * 1.0f / mDurationInMillis) * 100);
- progress = Math.max(Math.min(progress, 100), 0);
- }
- setProgress(progress);
- }
- });
- updateAppearance();
- }
-
- /**
- * Sets the duration of the audio that's being played, in milliseconds.
- */
- public void setDuration(final long durationInMillis) {
- mDurationInMillis = durationInMillis;
- }
-
- @Override
- public void restart() {
- reset();
- resume();
- }
-
- @Override
- public void reset() {
- stopUpdateTicks();
- setProgress(0);
- mCumulativeTime = 0;
- mCurrentPlayStartTime = 0;
- }
-
- @Override
- public void resume() {
- mCurrentPlayStartTime = SystemClock.elapsedRealtime();
- startUpdateTicks();
- }
-
- @Override
- public void pause() {
- mCumulativeTime += SystemClock.elapsedRealtime() - mCurrentPlayStartTime;
- stopUpdateTicks();
- }
-
- private void startUpdateTicks() {
- if (!mUpdateAnimator.isStarted()) {
- mUpdateAnimator.start();
- }
- }
-
- private void stopUpdateTicks() {
- if (mUpdateAnimator.isStarted()) {
- mUpdateAnimator.end();
- }
- }
-
- private void updateAppearance() {
- final Drawable drawable =
- ConversationDrawables.get().getAudioProgressDrawable(mIncoming);
- final ClipDrawable clipDrawable = new ClipDrawable(drawable, Gravity.START,
- ClipDrawable.HORIZONTAL);
- setProgressDrawable(clipDrawable);
- setBackground(ConversationDrawables.get()
- .getAudioProgressBackgroundDrawable(mIncoming));
- }
-
- public void setVisualStyle(final boolean incoming) {
- if (mIncoming != incoming) {
- mIncoming = incoming;
- updateAppearance();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/BaseBugleActivity.java b/src/com/android/messaging/ui/BaseBugleActivity.java
deleted file mode 100644
index 1236282..0000000
--- a/src/com/android/messaging/ui/BaseBugleActivity.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import com.android.messaging.util.BugleActivityUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Base class for app activities that would normally derive from Activity. Responsible for
- * ensuring app requirements are met during onResume()
- */
-public class BaseBugleActivity extends Activity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
- return;
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
- BugleActivityUtil.onActivityResume(this, BaseBugleActivity.this);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
- }
-}
diff --git a/src/com/android/messaging/ui/BaseBugleFragmentActivity.java b/src/com/android/messaging/ui/BaseBugleFragmentActivity.java
deleted file mode 100644
index 947970f..0000000
--- a/src/com/android/messaging/ui/BaseBugleFragmentActivity.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.Activity;
-
-import com.android.messaging.util.BugleActivityUtil;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Base class for app activities that would normally derive from FragmentActivity. Responsible for
- * ensuring app requirements are met during onResume()
- */
-public class BaseBugleFragmentActivity extends Activity {
- @Override
- protected void onResume() {
- super.onResume();
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
- // Ensure we have a sufficient version of Google Play Services, prompting for upgrade and
- // disabling the data updates if we don't have the correct version.
- BugleActivityUtil.onActivityResume(this, BaseBugleFragmentActivity.this);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
- }
-}
diff --git a/src/com/android/messaging/ui/BasePagerViewHolder.java b/src/com/android/messaging/ui/BasePagerViewHolder.java
deleted file mode 100644
index a82ecce..0000000
--- a/src/com/android/messaging/ui/BasePagerViewHolder.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.os.Parcelable;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * The base pager view holder implementation that handles basic view creation/destruction logic,
- * as well as logic to save/restore instance state that's persisted not only for activity
- * reconstruction (e.g. during a configuration change), but also cases where the individual
- * page view gets destroyed and recreated.
- *
- * To opt into saving/restoring instance state behavior for a particular page view, let the
- * PageView implement PersistentInstanceState to save and restore instance states to/from a
- * Parcelable.
- */
-public abstract class BasePagerViewHolder implements PagerViewHolder {
- protected View mView;
- protected Parcelable mSavedState;
-
- /**
- * This is called when the entire view pager is being torn down (due to configuration change
- * for example) that will be restored later.
- */
- @Override
- public Parcelable saveState() {
- savePendingState();
- return mSavedState;
- }
-
- /**
- * This is called when the view pager is being restored.
- */
- @Override
- public void restoreState(final Parcelable restoredState) {
- if (restoredState != null) {
- mSavedState = restoredState;
- // If the view is already there, let it restore the state. Otherwise, it will be
- // restored the next time the view gets created.
- restorePendingState();
- }
- }
-
- @Override
- public void resetState() {
- mSavedState = null;
- if (mView != null && (mView instanceof PersistentInstanceState)) {
- ((PersistentInstanceState) mView).resetState();
- }
- }
-
- /**
- * This is called when the view itself is being torn down. This may happen when the user
- * has flipped to another page in the view pager, so we want to save the current state if
- * possible.
- */
- @Override
- public View destroyView() {
- savePendingState();
- final View retView = mView;
- mView = null;
- return retView;
- }
-
- @Override
- public View getView(ViewGroup container) {
- if (mView == null) {
- mView = createView(container);
- // When initially created, check if the view has any saved state. If so restore it.
- restorePendingState();
- }
- return mView;
- }
-
- private void savePendingState() {
- if (mView != null && (mView instanceof PersistentInstanceState)) {
- mSavedState = ((PersistentInstanceState) mView).saveState();
- }
- }
-
- private void restorePendingState() {
- if (mView != null && (mView instanceof PersistentInstanceState) && (mSavedState != null)) {
- ((PersistentInstanceState) mView).restoreState(mSavedState);
- }
- }
-
- /**
- * Create and initialize a new page view.
- */
- protected abstract View createView(ViewGroup container);
-}
diff --git a/src/com/android/messaging/ui/BlockedParticipantListItemView.java b/src/com/android/messaging/ui/BlockedParticipantListItemView.java
deleted file mode 100644
index 2ccdebf..0000000
--- a/src/com/android/messaging/ui/BlockedParticipantListItemView.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantListItemData;
-
-/**
- * View for individual participant in blocked participants list.
- *
- * Unblocks participant when clicked.
- */
-public class BlockedParticipantListItemView extends LinearLayout {
- private TextView mNameTextView;
- private ContactIconView mContactIconView;
- private ParticipantListItemData mData;
-
- public BlockedParticipantListItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- mNameTextView = (TextView) findViewById(R.id.name);
- mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mData.unblock(getContext());
- }
- });
- }
-
- public void bind(final ParticipantListItemData data) {
- mData = data;
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- mNameTextView.setText(bidiFormatter.unicodeWrap(
- data.getDisplayName(), TextDirectionHeuristicsCompat.LTR));
- mContactIconView.setImageResourceUri(data.getAvatarUri(), data.getContactId(),
- data.getLookupKey(), data.getNormalizedDestination());
- mNameTextView.setText(data.getDisplayName());
- }
-}
diff --git a/src/com/android/messaging/ui/BlockedParticipantsActivity.java b/src/com/android/messaging/ui/BlockedParticipantsActivity.java
deleted file mode 100644
index b740264..0000000
--- a/src/com/android/messaging/ui/BlockedParticipantsActivity.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-/**
- * Show a list of currently blocked participants.
- */
-public class BlockedParticipantsActivity extends BugleActionBarActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.blocked_participants_activity);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- Assert.isTrue(fragment instanceof BlockedParticipantsFragment);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // Treat the home press as back press so that when we go back to
- // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
- onBackPressed();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/BlockedParticipantsFragment.java b/src/com/android/messaging/ui/BlockedParticipantsFragment.java
deleted file mode 100644
index 3ab54ab..0000000
--- a/src/com/android/messaging/ui/BlockedParticipantsFragment.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v4.widget.CursorAdapter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.BlockedParticipantsData;
-import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.util.Assert;
-
-/**
- * Show a list of currently blocked participants.
- */
-public class BlockedParticipantsFragment extends Fragment
- implements BlockedParticipantsDataListener {
- private ListView mListView;
- private BlockedParticipantListAdapter mAdapter;
- private final Binding<BlockedParticipantsData> mBinding =
- BindingBase.createBinding(this);
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view =
- inflater.inflate(R.layout.blocked_participants_fragment, container, false);
- mListView = (ListView) view.findViewById(android.R.id.list);
- mAdapter = new BlockedParticipantListAdapter(getActivity(), null);
- mListView.setAdapter(mAdapter);
- mBinding.bind(DataModel.get().createBlockedParticipantsData(getActivity(), this));
- mBinding.getData().init(getLoaderManager(), mBinding);
- return view;
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- /**
- * An adapter to display ParticipantListItemView based on ParticipantData.
- */
- private class BlockedParticipantListAdapter extends CursorAdapter {
- public BlockedParticipantListAdapter(final Context context, final Cursor cursor) {
- super(context, cursor, 0);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return LayoutInflater.from(context)
- .inflate(R.layout.blocked_participant_list_item_view, parent, false);
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- Assert.isTrue(view instanceof BlockedParticipantListItemView);
- ((BlockedParticipantListItemView) view).bind(
- mBinding.getData().createParticipantListItemData(cursor));
- }
- }
-
- @Override
- public void onBlockedParticipantsCursorUpdated(Cursor cursor) {
- mAdapter.swapCursor(cursor);
- }
-}
diff --git a/src/com/android/messaging/ui/BugleActionBarActivity.java b/src/com/android/messaging/ui/BugleActionBarActivity.java
deleted file mode 100644
index 80dabb8..0000000
--- a/src/com/android/messaging/ui/BugleActionBarActivity.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarActivity;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.util.BugleActivityUtil;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Base class for app activities that use an action bar. Responsible for logging/telemetry/other
- * needs that will be common for all activities. We can break out the common code if/when we need
- * a version that doesn't use an actionbar.
- */
-public class BugleActionBarActivity extends ActionBarActivity implements ImeUtil.ImeStateHost {
- // Tracks the list of observers opting in for IME state change.
- private final Set<ImeUtil.ImeStateObserver> mImeStateObservers = new HashSet<>();
-
- // Tracks the soft keyboard display state
- private boolean mImeOpen;
-
- // The ActionMode that represents the modal contextual action bar, using our own implementation
- // rather than the built in contextual action bar to reduce jank
- private CustomActionMode mActionMode;
-
- // The menu for the action bar
- private Menu mActionBarMenu;
-
- // Used to determine if a onDisplayHeightChanged was due to the IME opening or rotation of the
- // device
- private int mLastScreenHeight;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
- return;
- }
-
- mLastScreenHeight = getResources().getDisplayMetrics().heightPixels;
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onCreate");
- }
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStart");
- }
- }
-
- @Override
- protected void onRestart() {
- super.onStop();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onRestart");
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
- }
- BugleActivityUtil.onActivityResume(this, BugleActionBarActivity.this);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStop");
- }
- }
-
- private boolean mDestroyed;
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mDestroyed = true;
- }
-
- public boolean getIsDestroyed() {
- return mDestroyed;
- }
-
- @Override
- public void onDisplayHeightChanged(final int heightSpecification) {
- int screenHeight = getResources().getDisplayMetrics().heightPixels;
-
- if (screenHeight != mLastScreenHeight) {
- // Appears to be an orientation change, don't fire ime updates
- mLastScreenHeight = screenHeight;
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
- " screenHeight: " + screenHeight + " lastScreenHeight: " + mLastScreenHeight +
- " Skipped, appears to be orientation change.");
- return;
- }
- final ActionBar actionBar = getSupportActionBar();
- if (actionBar != null && actionBar.isShowing()) {
- screenHeight -= actionBar.getHeight();
- }
- final int height = View.MeasureSpec.getSize(heightSpecification);
-
- final boolean imeWasOpen = mImeOpen;
- mImeOpen = screenHeight - height > 100;
-
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
- "imeWasOpen: " + imeWasOpen + " mImeOpen: " + mImeOpen + " screenHeight: " +
- screenHeight + " height: " + height);
- }
-
- if (imeWasOpen != mImeOpen) {
- for (final ImeUtil.ImeStateObserver observer : mImeStateObservers) {
- observer.onImeStateChanged(mImeOpen);
- }
- }
- }
-
- @Override
- public void registerImeStateObserver(final ImeUtil.ImeStateObserver observer) {
- mImeStateObservers.add(observer);
- }
-
- @Override
- public void unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer) {
- mImeStateObservers.remove(observer);
- }
-
- @Override
- public boolean isImeOpen() {
- return mImeOpen;
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- mActionBarMenu = menu;
- if (mActionMode != null &&
- mActionMode.getCallback().onCreateActionMode(mActionMode, menu)) {
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(final Menu menu) {
- mActionBarMenu = menu;
- if (mActionMode != null &&
- mActionMode.getCallback().onPrepareActionMode(mActionMode, menu)) {
- return true;
- }
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem menuItem) {
- if (mActionMode != null &&
- mActionMode.getCallback().onActionItemClicked(mActionMode, menuItem)) {
- return true;
- }
-
- switch (menuItem.getItemId()) {
- case android.R.id.home:
- if (mActionMode != null) {
- dismissActionMode();
- return true;
- }
- }
- return super.onOptionsItemSelected(menuItem);
- }
-
- @Override
- public ActionMode startActionMode(final ActionMode.Callback callback) {
- mActionMode = new CustomActionMode(callback);
- supportInvalidateOptionsMenu();
- invalidateActionBar();
- return mActionMode;
- }
-
- public void dismissActionMode() {
- if (mActionMode != null) {
- mActionMode.finish();
- mActionMode = null;
- invalidateActionBar();
- }
- }
-
- public ActionMode getActionMode() {
- return mActionMode;
- }
-
- protected ActionMode.Callback getActionModeCallback() {
- if (mActionMode == null) {
- return null;
- }
-
- return mActionMode.getCallback();
- }
-
- /**
- * Receives and handles action bar invalidation request from sub-components of this activity.
- *
- * <p>Normally actions have sole control over the action bar, but in order to support seamless
- * transitions for components such as the full screen media picker, we have to let it take over
- * the action bar and then restore its state afterwards</p>
- *
- * <p>If a fragment does anything that may change the action bar, it should call this method
- * and then it is this method's responsibility to figure out which component "controls" the
- * action bar and delegate the updating of the action bar to that component</p>
- */
- public final void invalidateActionBar() {
- if (mActionMode != null) {
- mActionMode.updateActionBar(getSupportActionBar());
- } else {
- updateActionBar(getSupportActionBar());
- }
- }
-
- protected void updateActionBar(final ActionBar actionBar) {
- actionBar.setHomeAsUpIndicator(null);
- }
-
- /**
- * Custom ActionMode implementation which allows us to just replace the contents of the main
- * action bar rather than overlay over it
- */
- private class CustomActionMode extends ActionMode {
- private CharSequence mTitle;
- private CharSequence mSubtitle;
- private View mCustomView;
- private final Callback mCallback;
-
- public CustomActionMode(final Callback callback) {
- mCallback = callback;
- }
-
- @Override
- public void setTitle(final CharSequence title) {
- mTitle = title;
- }
-
- @Override
- public void setTitle(final int resId) {
- mTitle = getResources().getString(resId);
- }
-
- @Override
- public void setSubtitle(final CharSequence subtitle) {
- mSubtitle = subtitle;
- }
-
- @Override
- public void setSubtitle(final int resId) {
- mSubtitle = getResources().getString(resId);
- }
-
- @Override
- public void setCustomView(final View view) {
- mCustomView = view;
- }
-
- @Override
- public void invalidate() {
- invalidateActionBar();
- }
-
- @Override
- public void finish() {
- mActionMode = null;
- mCallback.onDestroyActionMode(this);
- supportInvalidateOptionsMenu();
- invalidateActionBar();
- }
-
- @Override
- public Menu getMenu() {
- return mActionBarMenu;
- }
-
- @Override
- public CharSequence getTitle() {
- return mTitle;
- }
-
- @Override
- public CharSequence getSubtitle() {
- return mSubtitle;
- }
-
- @Override
- public View getCustomView() {
- return mCustomView;
- }
-
- @Override
- public MenuInflater getMenuInflater() {
- return BugleActionBarActivity.this.getMenuInflater();
- }
-
- public Callback getCallback() {
- return mCallback;
- }
-
- public void updateActionBar(final ActionBar actionBar) {
- actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP);
- actionBar.setDisplayShowTitleEnabled(false);
- actionBar.setDisplayShowCustomEnabled(false);
- mActionMode.getCallback().onPrepareActionMode(mActionMode, mActionBarMenu);
- actionBar.setBackgroundDrawable(new ColorDrawable(
- getResources().getColor(R.color.contextual_action_bar_background_color)));
- actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_small_dark);
- actionBar.show();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/BugleAnimationTags.java b/src/com/android/messaging/ui/BugleAnimationTags.java
deleted file mode 100644
index b141f5b..0000000
--- a/src/com/android/messaging/ui/BugleAnimationTags.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-
-/**
- * Defines a list of animation tags used as android:transitionName properties used for L's
- * hero transitions.
- */
-public class BugleAnimationTags {
- /**
- * The tag for the FAB in the conversation list activity.
- */
- public static final String TAG_FABICON = "bugle:fabicon";
-
- /**
- * The tag for the content view of a conversation list item view.
- */
- public static final String TAG_CLIVCONTENT = "bugle:clivcontent";
-
- /**
- * The tag for the action bar.
- */
- public static final String TAG_ACTIONBAR = "bugle:actionbar";
-}
diff --git a/src/com/android/messaging/ui/ClassZeroActivity.java b/src/com/android/messaging/ui/ClassZeroActivity.java
deleted file mode 100644
index 129ec19..0000000
--- a/src/com/android/messaging/ui/ClassZeroActivity.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ContentValues;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.provider.Telephony.Sms;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Window;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.action.ReceiveSmsMessageAction;
-import com.android.messaging.util.Assert;
-
-import java.util.ArrayList;
-
-/**
- * Display a class-zero SMS message to the user. Wait for the user to dismiss
- * it.
- */
-public class ClassZeroActivity extends Activity {
- private static final boolean VERBOSE = false;
- private static final String TAG = "display_00";
- private static final int ON_AUTO_SAVE = 1;
-
- /** Default timer to dismiss the dialog. */
- private static final long DEFAULT_TIMER = 5 * 60 * 1000;
-
- /** To remember the exact time when the timer should fire. */
- private static final String TIMER_FIRE = "timer_fire";
-
- private ContentValues mMessageValues = null;
-
- /** Is the message read. */
- private boolean mRead = false;
-
- /** The timer to dismiss the dialog automatically. */
- private long mTimerSet = 0;
- private AlertDialog mDialog = null;
-
- private ArrayList<ContentValues> mMessageQueue = null;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(final Message msg) {
- // Do not handle an invalid message.
- if (msg.what == ON_AUTO_SAVE) {
- mRead = false;
- mDialog.dismiss();
- saveMessage();
- processNextMessage();
- }
- }
- };
-
- private boolean queueMsgFromIntent(final Intent msgIntent) {
- final ContentValues messageValues =
- msgIntent.getParcelableExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_VALUES);
- // that takes the format argument is a hidden API right now.
- final String message = messageValues.getAsString(Sms.BODY);
- if (TextUtils.isEmpty(message)) {
- if (mMessageQueue.size() == 0) {
- finish();
- }
- return false;
- }
- mMessageQueue.add(messageValues);
- return true;
- }
-
- private void processNextMessage() {
- if (mMessageQueue.size() > 0) {
- mMessageQueue.remove(0);
- }
- if (mMessageQueue.size() == 0) {
- finish();
- } else {
- displayZeroMessage(mMessageQueue.get(0));
- }
- }
-
- private void saveMessage() {
- mMessageValues.put(Sms.Inbox.READ, mRead ? Integer.valueOf(1) : Integer.valueOf(0));
- final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(mMessageValues);
- action.start();
- }
-
- @Override
- protected void onCreate(final Bundle icicle) {
- super.onCreate(icicle);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
-
- if (mMessageQueue == null) {
- mMessageQueue = new ArrayList<ContentValues>();
- }
- if (!queueMsgFromIntent(getIntent())) {
- return;
- }
- Assert.isTrue(mMessageQueue.size() == 1);
- if (mMessageQueue.size() == 1) {
- displayZeroMessage(mMessageQueue.get(0));
- }
-
- if (icicle != null) {
- mTimerSet = icicle.getLong(TIMER_FIRE, mTimerSet);
- }
- }
-
- private void displayZeroMessage(final ContentValues messageValues) {
- /* This'll be used by the save action */
- mMessageValues = messageValues;
- final String message = messageValues.getAsString(Sms.BODY);;
-
- mDialog = new AlertDialog.Builder(this).setMessage(message)
- .setPositiveButton(R.string.save, mSaveListener)
- .setNegativeButton(android.R.string.cancel, mCancelListener)
- .setTitle(R.string.class_0_message_activity)
- .setCancelable(false).show();
- final long now = SystemClock.uptimeMillis();
- mTimerSet = now + DEFAULT_TIMER;
- }
-
- @Override
- protected void onNewIntent(final Intent msgIntent) {
- // Running with another visible message, queue this one
- queueMsgFromIntent(msgIntent);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- final long now = SystemClock.uptimeMillis();
- if (mTimerSet <= now) {
- // Save the message if the timer already expired.
- mHandler.sendEmptyMessage(ON_AUTO_SAVE);
- } else {
- mHandler.sendEmptyMessageAtTime(ON_AUTO_SAVE, mTimerSet);
- if (VERBOSE) {
- Log.d(TAG, "onRestart time = " + Long.toString(mTimerSet) + " "
- + this.toString());
- }
- }
- }
-
- @Override
- protected void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putLong(TIMER_FIRE, mTimerSet);
- if (VERBOSE) {
- Log.d(TAG, "onSaveInstanceState time = " + Long.toString(mTimerSet)
- + " " + this.toString());
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mHandler.removeMessages(ON_AUTO_SAVE);
- if (VERBOSE) {
- Log.d(TAG, "onStop time = " + Long.toString(mTimerSet)
- + " " + this.toString());
- }
- }
-
- private final OnClickListener mCancelListener = new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int whichButton) {
- dialog.dismiss();
- processNextMessage();
- }
- };
-
- private final OnClickListener mSaveListener = new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int whichButton) {
- mRead = true;
- saveMessage();
- dialog.dismiss();
- processNextMessage();
- }
- };
-}
diff --git a/src/com/android/messaging/ui/CompositeAdapter.java b/src/com/android/messaging/ui/CompositeAdapter.java
deleted file mode 100644
index 620e511..0000000
--- a/src/com/android/messaging/ui/CompositeAdapter.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-/**
- * A general purpose adapter that composes one or more other adapters. It
- * appends them in the order they are added.
- */
-public class CompositeAdapter extends BaseAdapter {
-
- private static final int INITIAL_CAPACITY = 2;
-
- public static class Partition {
- boolean mShowIfEmpty;
- boolean mHasHeader;
- BaseAdapter mAdapter;
-
- public Partition(final boolean showIfEmpty, final boolean hasHeader,
- final BaseAdapter adapter) {
- this.mShowIfEmpty = showIfEmpty;
- this.mHasHeader = hasHeader;
- this.mAdapter = adapter;
- }
-
- /**
- * True if the directory should be shown even if no contacts are found.
- */
- public boolean showIfEmpty() {
- return mShowIfEmpty;
- }
-
- public boolean hasHeader() {
- return mHasHeader;
- }
-
- public int getCount() {
- int count = mAdapter.getCount();
- if (mHasHeader && (count != 0 || mShowIfEmpty)) {
- count++;
- }
- return count;
- }
-
- public BaseAdapter getAdapter() {
- return mAdapter;
- }
-
- public View getHeaderView(final View convertView, final ViewGroup parentView) {
- return null;
- }
-
- public void close() {
- // do nothing in base class.
- }
- }
-
- private class Observer extends DataSetObserver {
- @Override
- public void onChanged() {
- CompositeAdapter.this.notifyDataSetChanged();
- }
-
- @Override
- public void onInvalidated() {
- CompositeAdapter.this.notifyDataSetInvalidated();
- }
- };
-
- protected final Context mContext;
- private Partition[] mPartitions;
- private int mSize = 0;
- private int mCount = 0;
- private boolean mCacheValid = true;
- private final Observer mObserver;
-
- public CompositeAdapter(final Context context) {
- mContext = context;
- mObserver = new Observer();
- mPartitions = new Partition[INITIAL_CAPACITY];
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public void addPartition(final Partition partition) {
- if (mSize >= mPartitions.length) {
- final int newCapacity = mSize + 2;
- final Partition[] newAdapters = new Partition[newCapacity];
- System.arraycopy(mPartitions, 0, newAdapters, 0, mSize);
- mPartitions = newAdapters;
- }
- mPartitions[mSize++] = partition;
- partition.getAdapter().registerDataSetObserver(mObserver);
- invalidate();
- notifyDataSetChanged();
- }
-
- public void removePartition(final int index) {
- final Partition partition = mPartitions[index];
- partition.close();
- System.arraycopy(mPartitions, index + 1, mPartitions, index,
- mSize - index - 1);
- mSize--;
- partition.getAdapter().unregisterDataSetObserver(mObserver);
- invalidate();
- notifyDataSetChanged();
- }
-
- public void clearPartitions() {
- for (int i = 0; i < mSize; i++) {
- final Partition partition = mPartitions[i];
- partition.close();
- partition.getAdapter().unregisterDataSetObserver(mObserver);
- }
- invalidate();
- notifyDataSetChanged();
- }
-
- public Partition getPartition(final int index) {
- return mPartitions[index];
- }
-
- public int getPartitionAtPosition(final int position) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final int end = start + mPartitions[i].getCount();
- if (position >= start && position < end) {
- int offset = position - start;
- if (mPartitions[i].hasHeader() &&
- (mPartitions[i].getCount() > 0 || mPartitions[i].showIfEmpty())) {
- offset--;
- }
- if (offset == -1) {
- return -1;
- }
- return i;
- }
- start = end;
- }
- return mSize - 1;
- }
-
- public int getPartitionCount() {
- return mSize;
- }
-
- public void invalidate() {
- mCacheValid = false;
- }
-
- private void ensureCacheValid() {
- if (mCacheValid) {
- return;
- }
- mCount = 0;
- for (int i = 0; i < mSize; i++) {
- mCount += mPartitions[i].getCount();
- }
- }
-
- @Override
- public int getCount() {
- ensureCacheValid();
- return mCount;
- }
-
- public int getCount(final int index) {
- ensureCacheValid();
- return mPartitions[index].getCount();
- }
-
- @Override
- public Object getItem(final int position) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final int end = start + mPartitions[i].getCount();
- if (position >= start && position < end) {
- final int offset = position - start;
- final Partition partition = mPartitions[i];
- if (partition.hasHeader() && offset == 0 &&
- (partition.getCount() > 0 || partition.showIfEmpty())) {
- // This is the header
- return null;
- }
- return mPartitions[i].getAdapter().getItem(offset);
- }
- start = end;
- }
-
- return null;
- }
-
- @Override
- public long getItemId(final int position) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final int end = start + mPartitions[i].getCount();
- if (position >= start && position < end) {
- final int offset = position - start;
- final Partition partition = mPartitions[i];
- if (partition.hasHeader() && offset == 0 &&
- (partition.getCount() > 0 || partition.showIfEmpty())) {
- // Header
- return 0;
- }
- return mPartitions[i].getAdapter().getItemId(offset);
- }
- start = end;
- }
-
- return 0;
- }
-
- @Override
- public boolean isEnabled(int position) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final int end = start + mPartitions[i].getCount();
- if (position >= start && position < end) {
- final int offset = position - start;
- final Partition partition = mPartitions[i];
- if (partition.hasHeader() && offset == 0 &&
- (partition.getCount() > 0 || partition.showIfEmpty())) {
- // This is the header
- return false;
- }
- return true;
- }
- start = end;
- }
- return true;
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parentView) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final Partition partition = mPartitions[i];
- final int end = start + partition.getCount();
- if (position >= start && position < end) {
- int offset = position - start;
- View view;
- if (partition.hasHeader() &&
- (partition.getCount() > 0 || partition.showIfEmpty())) {
- offset = offset - 1;
- }
- if (offset == -1) {
- view = partition.getHeaderView(convertView, parentView);
- } else {
- view = partition.getAdapter().getView(offset, convertView, parentView);
- }
- if (view == null) {
- throw new NullPointerException("View should not be null, partition: " + i
- + " position: " + offset);
- }
- return view;
- }
- start = end;
- }
-
- throw new ArrayIndexOutOfBoundsException(position);
- }
-}
diff --git a/src/com/android/messaging/ui/ContactIconView.java b/src/com/android/messaging/ui/ContactIconView.java
deleted file mode 100644
index 44983ab..0000000
--- a/src/com/android/messaging/ui/ContactIconView.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor;
-import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ContactUtil;
-
-/**
- * A view used to render contact icons. This class derives from AsyncImageView, so it loads contact
- * icons from MediaResourceManager, and it handles more rendering logic than an AsyncImageView
- * (draws a circular bitmap).
- */
-public class ContactIconView extends AsyncImageView {
- private static final int NORMAL_ICON_SIZE_ID = 0;
- private static final int LARGE_ICON_SIZE_ID = 1;
- private static final int SMALL_ICON_SIZE_ID = 2;
-
- protected final int mIconSize;
- private final int mColorPressedId;
-
- private long mContactId;
- private String mContactLookupKey;
- private String mNormalizedDestination;
- private Uri mAvatarUri;
- private boolean mDisableClickHandler;
-
- public ContactIconView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- final Resources resources = context.getResources();
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ContactIconView);
-
- final int iconSizeId = a.getInt(R.styleable.ContactIconView_iconSize, 0);
- switch (iconSizeId) {
- case NORMAL_ICON_SIZE_ID:
- mIconSize = (int) resources.getDimension(
- R.dimen.contact_icon_view_normal_size);
- break;
- case LARGE_ICON_SIZE_ID:
- mIconSize = (int) resources.getDimension(
- R.dimen.contact_icon_view_large_size);
- break;
- case SMALL_ICON_SIZE_ID:
- mIconSize = (int) resources.getDimension(
- R.dimen.contact_icon_view_small_size);
- break;
- default:
- // For the compiler, something has to be set even with the assert.
- mIconSize = 0;
- Assert.fail("Unsupported ContactIconView icon size attribute");
- }
- mColorPressedId = resources.getColor(R.color.contact_avatar_pressed_color);
-
- setImage(null);
- a.recycle();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- setColorFilter(mColorPressedId);
- } else {
- clearColorFilter();
- }
- return super.onTouchEvent(event);
- }
-
- /**
- * Method which allows the automatic hookup of a click handler when the Uri is changed
- */
- public void setImageClickHandlerDisabled(final boolean isHandlerDisabled) {
- mDisableClickHandler = isHandlerDisabled;
- setOnClickListener(null);
- setClickable(false);
- }
-
- /**
- * A convenience method that sets the URI of the contact icon by creating a new image request.
- */
- public void setImageResourceUri(final Uri uri) {
- setImageResourceUri(uri, 0, null, null);
- }
-
- public void setImageResourceUri(final Uri uri, final long contactId,
- final String contactLookupKey, final String normalizedDestination) {
- if (uri == null) {
- setImageResourceId(null);
- } else {
- final String avatarType = AvatarUriUtil.getAvatarType(uri);
- if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) {
- setImageResourceId(new AvatarGroupRequestDescriptor(uri, mIconSize, mIconSize));
- } else {
- setImageResourceId(new AvatarRequestDescriptor(uri, mIconSize, mIconSize));
- }
- }
-
- mContactId = contactId;
- mContactLookupKey = contactLookupKey;
- mNormalizedDestination = normalizedDestination;
- mAvatarUri = uri;
-
- maybeInitializeOnClickListener();
- }
-
- protected void maybeInitializeOnClickListener() {
- if ((mContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
- && !TextUtils.isEmpty(mContactLookupKey)) ||
- !TextUtils.isEmpty(mNormalizedDestination)) {
- if (!mDisableClickHandler) {
- setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- ContactUtil.showOrAddContact(view, mContactId, mContactLookupKey,
- mAvatarUri, mNormalizedDestination);
- }
- });
- }
- } else {
- // This should happen when the phone number is not in the user's contacts or it is a
- // group conversation, group conversations don't have contact phone numbers. If this
- // is the case then absorb the click to prevent propagation.
- setOnClickListener(null);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/ConversationDrawables.java b/src/com/android/messaging/ui/ConversationDrawables.java
deleted file mode 100644
index cf858e2..0000000
--- a/src/com/android/messaging/ui/ConversationDrawables.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.ImageUtils;
-
-/**
- * A singleton cache that holds tinted drawable resources for displaying messages, such as
- * message bubbles, audio attachments etc.
- */
-public class ConversationDrawables {
- private static ConversationDrawables sInstance;
-
- // Cache the color filtered bubble drawables so that we don't need to create a
- // new one for each ConversationMessageView.
- private Drawable mIncomingBubbleDrawable;
- private Drawable mOutgoingBubbleDrawable;
- private Drawable mIncomingErrorBubbleDrawable;
- private Drawable mIncomingBubbleNoArrowDrawable;
- private Drawable mOutgoingBubbleNoArrowDrawable;
- private Drawable mAudioPlayButtonDrawable;
- private Drawable mAudioPauseButtonDrawable;
- private Drawable mIncomingAudioProgressBackgroundDrawable;
- private Drawable mOutgoingAudioProgressBackgroundDrawable;
- private Drawable mAudioProgressForegroundDrawable;
- private Drawable mFastScrollThumbDrawable;
- private Drawable mFastScrollThumbPressedDrawable;
- private Drawable mFastScrollPreviewDrawableLeft;
- private Drawable mFastScrollPreviewDrawableRight;
- private final Context mContext;
- private int mOutgoingBubbleColor;
- private int mIncomingErrorBubbleColor;
- private int mIncomingAudioButtonColor;
- private int mSelectedBubbleColor;
- private int mThemeColor;
-
- public static ConversationDrawables get() {
- if (sInstance == null) {
- sInstance = new ConversationDrawables(Factory.get().getApplicationContext());
- }
- return sInstance;
- }
-
- private ConversationDrawables(final Context context) {
- mContext = context;
- // Pre-create all the drawables.
- updateDrawables();
- }
-
- public int getConversationThemeColor() {
- return mThemeColor;
- }
-
- public void updateDrawables() {
- final Resources resources = mContext.getResources();
-
- mIncomingBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_incoming);
- mIncomingBubbleNoArrowDrawable =
- resources.getDrawable(R.drawable.message_bubble_incoming_no_arrow);
- mIncomingErrorBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_error);
- mOutgoingBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_outgoing);
- mOutgoingBubbleNoArrowDrawable =
- resources.getDrawable(R.drawable.message_bubble_outgoing_no_arrow);
- mAudioPlayButtonDrawable = resources.getDrawable(R.drawable.ic_audio_play);
- mAudioPauseButtonDrawable = resources.getDrawable(R.drawable.ic_audio_pause);
- mIncomingAudioProgressBackgroundDrawable =
- resources.getDrawable(R.drawable.audio_progress_bar_background_incoming);
- mOutgoingAudioProgressBackgroundDrawable =
- resources.getDrawable(R.drawable.audio_progress_bar_background_outgoing);
- mAudioProgressForegroundDrawable =
- resources.getDrawable(R.drawable.audio_progress_bar_progress);
- mFastScrollThumbDrawable = resources.getDrawable(R.drawable.fastscroll_thumb);
- mFastScrollThumbPressedDrawable =
- resources.getDrawable(R.drawable.fastscroll_thumb_pressed);
- mFastScrollPreviewDrawableLeft =
- resources.getDrawable(R.drawable.fastscroll_preview_left);
- mFastScrollPreviewDrawableRight =
- resources.getDrawable(R.drawable.fastscroll_preview_right);
- mOutgoingBubbleColor = resources.getColor(R.color.message_bubble_color_outgoing);
- mIncomingErrorBubbleColor =
- resources.getColor(R.color.message_error_bubble_color_incoming);
- mIncomingAudioButtonColor =
- resources.getColor(R.color.message_audio_button_color_incoming);
- mSelectedBubbleColor = resources.getColor(R.color.message_bubble_color_selected);
- mThemeColor = resources.getColor(R.color.primary_color);
- }
-
- public Drawable getBubbleDrawable(final boolean selected, final boolean incoming,
- final boolean needArrow, final boolean isError) {
- final Drawable protoDrawable;
- if (needArrow) {
- if (incoming) {
- protoDrawable = isError && !selected ?
- mIncomingErrorBubbleDrawable : mIncomingBubbleDrawable;
- } else {
- protoDrawable = mOutgoingBubbleDrawable;
- }
- } else if (incoming) {
- protoDrawable = mIncomingBubbleNoArrowDrawable;
- } else {
- protoDrawable = mOutgoingBubbleNoArrowDrawable;
- }
-
- int color;
- if (selected) {
- color = mSelectedBubbleColor;
- } else if (incoming) {
- if (isError) {
- color = mIncomingErrorBubbleColor;
- } else {
- color = mThemeColor;
- }
- } else {
- color = mOutgoingBubbleColor;
- }
-
- return ImageUtils.getTintedDrawable(mContext, protoDrawable, color);
- }
-
- private int getAudioButtonColor(final boolean incoming) {
- return incoming ? mIncomingAudioButtonColor : mThemeColor;
- }
-
- public Drawable getPlayButtonDrawable(final boolean incoming) {
- return ImageUtils.getTintedDrawable(
- mContext, mAudioPlayButtonDrawable, getAudioButtonColor(incoming));
- }
-
- public Drawable getPauseButtonDrawable(final boolean incoming) {
- return ImageUtils.getTintedDrawable(
- mContext, mAudioPauseButtonDrawable, getAudioButtonColor(incoming));
- }
-
- public Drawable getAudioProgressDrawable(final boolean incoming) {
- return ImageUtils.getTintedDrawable(
- mContext, mAudioProgressForegroundDrawable, getAudioButtonColor(incoming));
- }
-
- public Drawable getAudioProgressBackgroundDrawable(final boolean incoming) {
- return incoming ? mIncomingAudioProgressBackgroundDrawable :
- mOutgoingAudioProgressBackgroundDrawable;
- }
-
- public Drawable getFastScrollThumbDrawable(final boolean pressed) {
- if (pressed) {
- return ImageUtils.getTintedDrawable(mContext, mFastScrollThumbPressedDrawable,
- mThemeColor);
- } else {
- return mFastScrollThumbDrawable;
- }
- }
-
- public Drawable getFastScrollPreviewDrawable(boolean positionRight) {
- Drawable protoDrawable = positionRight ? mFastScrollPreviewDrawableRight :
- mFastScrollPreviewDrawableLeft;
- return ImageUtils.getTintedDrawable(mContext, protoDrawable, mThemeColor);
- }
-}
diff --git a/src/com/android/messaging/ui/CursorRecyclerAdapter.java b/src/com/android/messaging/ui/CursorRecyclerAdapter.java
deleted file mode 100644
index f1a7b7d..0000000
--- a/src/com/android/messaging/ui/CursorRecyclerAdapter.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.os.Handler;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.ViewGroup;
-import android.widget.FilterQueryProvider;
-
-/**
- * Copy of CursorAdapter suited for RecyclerView.
- *
- * TODO: BUG 16327984. Replace this with a framework supported CursorAdapter for
- * RecyclerView when one is available.
- */
-public abstract class CursorRecyclerAdapter<VH extends RecyclerView.ViewHolder>
- extends RecyclerView.Adapter<VH> {
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected boolean mDataValid;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected boolean mAutoRequery;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected Cursor mCursor;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected Context mContext;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected int mRowIDColumn;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected ChangeObserver mChangeObserver;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected DataSetObserver mDataSetObserver;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected FilterQueryProvider mFilterQueryProvider;
-
- /**
- * If set the adapter will call requery() on the cursor whenever a content change
- * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
- *
- * @deprecated This option is discouraged, as it results in Cursor queries
- * being performed on the application's UI thread and thus can cause poor
- * responsiveness or even Application Not Responding errors. As an alternative,
- * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
- */
- @Deprecated
- public static final int FLAG_AUTO_REQUERY = 0x01;
-
- /**
- * If set the adapter will register a content observer on the cursor and will call
- * {@link #onContentChanged()} when a notification comes in. Be careful when
- * using this flag: you will need to unset the current Cursor from the adapter
- * to avoid leaks due to its registered observers. This flag is not needed
- * when using a CursorAdapter with a
- * {@link android.content.CursorLoader}.
- */
- public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
-
- /**
- * Recommended constructor.
- *
- * @param c The cursor from which to get the data.
- * @param context The context
- * @param flags Flags used to determine the behavior of the adapter; may
- * be any combination of {@link #FLAG_AUTO_REQUERY} and
- * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
- */
- public CursorRecyclerAdapter(final Context context, final Cursor c, final int flags) {
- init(context, c, flags);
- }
-
- void init(final Context context, final Cursor c, int flags) {
- if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
- flags |= FLAG_REGISTER_CONTENT_OBSERVER;
- mAutoRequery = true;
- } else {
- mAutoRequery = false;
- }
- final boolean cursorPresent = c != null;
- mCursor = c;
- mDataValid = cursorPresent;
- mContext = context;
- mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
- if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
- mChangeObserver = new ChangeObserver();
- mDataSetObserver = new MyDataSetObserver();
- } else {
- mChangeObserver = null;
- mDataSetObserver = null;
- }
-
- if (cursorPresent) {
- if (mChangeObserver != null) {
- c.registerContentObserver(mChangeObserver);
- }
- if (mDataSetObserver != null) {
- c.registerDataSetObserver(mDataSetObserver);
- }
- }
- }
-
- /**
- * Returns the cursor.
- * @return the cursor.
- */
- public Cursor getCursor() {
- return mCursor;
- }
-
- @Override
- public int getItemCount() {
- if (mDataValid && mCursor != null) {
- return mCursor.getCount();
- } else {
- return 0;
- }
- }
-
- /**
- * @see android.support.v7.widget.RecyclerView.Adapter#getItem(int)
- */
- public Object getItem(final int position) {
- if (mDataValid && mCursor != null) {
- mCursor.moveToPosition(position);
- return mCursor;
- } else {
- return null;
- }
- }
-
- /**
- * @see android.support.v7.widget.RecyclerView.Adapter#getItemId(int)
- */
- @Override
- public long getItemId(final int position) {
- if (mDataValid && mCursor != null) {
- if (mCursor.moveToPosition(position)) {
- return mCursor.getLong(mRowIDColumn);
- } else {
- return 0;
- }
- } else {
- return 0;
- }
- }
-
- @Override
- public VH onCreateViewHolder(final ViewGroup parent, final int viewType) {
- return createViewHolder(mContext, parent, viewType);
- }
-
- @Override
- public void onBindViewHolder(final VH holder, final int position) {
- if (!mDataValid) {
- throw new IllegalStateException("this should only be called when the cursor is valid");
- }
- if (!mCursor.moveToPosition(position)) {
- throw new IllegalStateException("couldn't move cursor to position " + position);
- }
- bindViewHolder(holder, mContext, mCursor);
- }
- /**
- * Bind an existing view to the data pointed to by cursor
- * @param view Existing view, returned earlier by newView
- * @param context Interface to application's global information
- * @param cursor The cursor from which to get the data. The cursor is already
- * moved to the correct position.
- */
- public abstract void bindViewHolder(VH holder, Context context, Cursor cursor);
-
- /**
- * @see android.support.v7.widget.RecyclerView.Adapter#createViewHolder(Context, ViewGroup, int)
- */
- public abstract VH createViewHolder(Context context, ViewGroup parent, int viewType);
-
- /**
- * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
- * closed.
- *
- * @param cursor The new cursor to be used
- */
- public void changeCursor(final Cursor cursor) {
- final Cursor old = swapCursor(cursor);
- if (old != null) {
- old.close();
- }
- }
-
- /**
- * Swap in a new Cursor, returning the old Cursor. Unlike
- * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
- * closed.
- *
- * @param newCursor The new cursor to be used.
- * @return Returns the previously set Cursor, or null if there wasa not one.
- * If the given new Cursor is the same instance is the previously set
- * Cursor, null is also returned.
- */
- public Cursor swapCursor(final Cursor newCursor) {
- if (newCursor == mCursor) {
- return null;
- }
- final Cursor oldCursor = mCursor;
- if (oldCursor != null) {
- if (mChangeObserver != null) {
- oldCursor.unregisterContentObserver(mChangeObserver);
- }
- if (mDataSetObserver != null) {
- oldCursor.unregisterDataSetObserver(mDataSetObserver);
- }
- }
- mCursor = newCursor;
- if (newCursor != null) {
- if (mChangeObserver != null) {
- newCursor.registerContentObserver(mChangeObserver);
- }
- if (mDataSetObserver != null) {
- newCursor.registerDataSetObserver(mDataSetObserver);
- }
- mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
- mDataValid = true;
- // notify the observers about the new cursor
- notifyDataSetChanged();
- } else {
- mRowIDColumn = -1;
- mDataValid = false;
- // notify the observers about the lack of a data set
- notifyDataSetChanged();
- }
- return oldCursor;
- }
-
- /**
- * <p>Converts the cursor into a CharSequence. Subclasses should override this
- * method to convert their results. The default implementation returns an
- * empty String for null values or the default String representation of
- * the value.</p>
- *
- * @param cursor the cursor to convert to a CharSequence
- * @return a CharSequence representing the value
- */
- public CharSequence convertToString(final Cursor cursor) {
- return cursor == null ? "" : cursor.toString();
- }
-
- /**
- * Called when the {@link ContentObserver} on the cursor receives a change notification.
- * The default implementation provides the auto-requery logic, but may be overridden by
- * sub classes.
- *
- * @see ContentObserver#onChange(boolean)
- */
- protected void onContentChanged() {
- if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
- if (false) {
- Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
- }
- mDataValid = mCursor.requery();
- }
- }
-
- private class ChangeObserver extends ContentObserver {
- public ChangeObserver() {
- super(new Handler());
- }
-
- @Override
- public boolean deliverSelfNotifications() {
- return true;
- }
-
- @Override
- public void onChange(final boolean selfChange) {
- onContentChanged();
- }
- }
-
- private class MyDataSetObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- mDataValid = true;
- notifyDataSetChanged();
- }
-
- @Override
- public void onInvalidated() {
- mDataValid = false;
- notifyDataSetChanged();
- }
- }
-
-}
diff --git a/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java b/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java
deleted file mode 100644
index 1268c53..0000000
--- a/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.CursorAdapter;
-import android.widget.ListView;
-
-import com.android.messaging.util.ImeUtil;
-
-/**
- * Produces and holds a list view and its tab header to be displayed in a ViewPager.
- */
-public abstract class CustomHeaderPagerListViewHolder extends BasePagerViewHolder
- implements CustomHeaderPagerViewHolder {
- private final Context mContext;
- private final CursorAdapter mListAdapter;
- private boolean mListCursorInitialized;
- private ListView mListView;
-
- public CustomHeaderPagerListViewHolder(final Context context,
- final CursorAdapter adapter) {
- mContext = context;
- mListAdapter = adapter;
- }
-
- @Override
- protected View createView(ViewGroup container) {
- final LayoutInflater inflater = (LayoutInflater)
- mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- final View view = inflater.inflate(
- getLayoutResId(),
- null /* root */,
- false /* attachToRoot */);
- final ListView listView = (ListView) view.findViewById(getListViewResId());
- listView.setAdapter(mListAdapter);
- listView.setOnScrollListener(new OnScrollListener() {
- @Override
- public void onScrollStateChanged(final AbsListView view, final int scrollState) {
- if (scrollState != SCROLL_STATE_IDLE) {
- ImeUtil.get().hideImeKeyboard(mContext, view);
- }
- }
-
- @Override
- public void onScroll(final AbsListView view, final int firstVisibleItem,
- final int visibleItemCount, final int totalItemCount) {
- }
- });
- mListView = listView;
- maybeSetEmptyView();
- return view;
- }
-
- public void onContactsCursorUpdated(final Cursor data) {
- mListAdapter.swapCursor(data);
- if (!mListCursorInitialized) {
- // We set the emptyView here instead of in create so that the initial load won't show
- // the empty UI - the system handles this and doesn't do what we would like.
- mListCursorInitialized = true;
- maybeSetEmptyView();
- }
- }
-
- /**
- * We don't want to show the empty view hint until BOTH conditions are met:
- * 1. The view has been created.
- * 2. Cursor data has been loaded once.
- * Due to timing when data is loaded, the view may not be ready (and vice versa). So we
- * are calling this method from both onContactsCursorUpdated & createView.
- */
- private void maybeSetEmptyView() {
- if (mView != null && mListCursorInitialized) {
- final ListEmptyView emptyView = (ListEmptyView) mView.findViewById(getEmptyViewResId());
- if (emptyView != null) {
- emptyView.setTextHint(getEmptyViewTitleResId());
- emptyView.setImageHint(getEmptyViewImageResId());
- final ListView listView = (ListView) mView.findViewById(getListViewResId());
- listView.setEmptyView(emptyView);
- }
- }
- }
-
- public void invalidateList() {
- mListAdapter.notifyDataSetChanged();
- }
-
- /**
- * In order for scene transition to work, we toggle the visibility for each individual list
- * view items so that they can be properly tracked by the scene transition manager.
- * @param show whether the pending transition is to show or hide the list.
- */
- public void toggleVisibilityForPendingTransition(final boolean show, final View epicenterView) {
- if (mListView == null) {
- return;
- }
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View childView = mListView.getChildAt(i);
- if (childView != epicenterView) {
- childView.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
- }
- }
- }
-
- @Override
- public CharSequence getPageTitle(Context context) {
- return context.getString(getPageTitleResId());
- }
-
- protected abstract int getLayoutResId();
- protected abstract int getPageTitleResId();
- protected abstract int getEmptyViewResId();
- protected abstract int getEmptyViewTitleResId();
- protected abstract int getEmptyViewImageResId();
- protected abstract int getListViewResId();
-}
diff --git a/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java b/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java
deleted file mode 100644
index 43802cd..0000000
--- a/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-
-/**
- * An extension on the standard PagerViewHolder to return a custom header view to be used by
- * CustomHeaderViewPager
- */
-public interface CustomHeaderPagerViewHolder extends PagerViewHolder {
- CharSequence getPageTitle(Context context);
-}
diff --git a/src/com/android/messaging/ui/CustomHeaderViewPager.java b/src/com/android/messaging/ui/CustomHeaderViewPager.java
deleted file mode 100644
index 2e8df42..0000000
--- a/src/com/android/messaging/ui/CustomHeaderViewPager.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.support.v4.view.ViewPager.OnPageChangeListener;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-/**
- * A view that contains both a view pager and a tab strip wrapped in a linear layout.
- */
-public class CustomHeaderViewPager extends LinearLayout {
- public final static int DEFAULT_TAB_STRIP_SIZE = -1;
- private final int mDefaultTabStripSize;
- private ViewPager mViewPager;
- private ViewPagerTabs mTabstrip;
-
- public CustomHeaderViewPager(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- inflater.inflate(R.layout.custom_header_view_pager, this, true);
- setOrientation(LinearLayout.VERTICAL);
-
- mTabstrip = (ViewPagerTabs) findViewById(R.id.tab_strip);
- mViewPager = (ViewPager) findViewById(R.id.pager);
-
- TypedValue tv = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
- mDefaultTabStripSize = context.getResources().getDimensionPixelSize(tv.resourceId);
- }
-
- public void setCurrentItem(final int position) {
- mViewPager.setCurrentItem(position);
- }
-
- public void setViewPagerTabHeight(final int tabHeight) {
- mTabstrip.getLayoutParams().height = tabHeight == DEFAULT_TAB_STRIP_SIZE ?
- mDefaultTabStripSize : tabHeight;
- }
-
- public void setViewHolders(final CustomHeaderPagerViewHolder[] viewHolders) {
- Assert.notNull(mViewPager);
- final PagerAdapter adapter = new CustomHeaderViewPagerAdapter(viewHolders);
- mViewPager.setAdapter(adapter);
- mTabstrip.setViewPager(mViewPager);
- mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
-
- @Override
- public void onPageScrollStateChanged(int state) {
- mTabstrip.onPageScrollStateChanged(state);
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset,
- int positionOffsetPixels) {
- mTabstrip.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
-
- @Override
- public void onPageSelected(int position) {
- mTabstrip.onPageSelected(position);
- }
- });
- }
-
- public int getSelectedItemPosition() {
- return mTabstrip.getSelectedItemPosition();
- }
-}
diff --git a/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java b/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java
deleted file mode 100644
index 1a5cf6a..0000000
--- a/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import com.android.messaging.Factory;
-
-public class CustomHeaderViewPagerAdapter extends
- FixedViewPagerAdapter<CustomHeaderPagerViewHolder> {
-
- public CustomHeaderViewPagerAdapter(final CustomHeaderPagerViewHolder[] viewHolders) {
- super(viewHolders);
- }
-
- @Override
- public CharSequence getPageTitle(int position) {
- // The tab strip will handle RTL internally so we should use raw position.
- return getViewHolder(position, false /* rtlAware */)
- .getPageTitle(Factory.get().getApplicationContext());
- }
-}
diff --git a/src/com/android/messaging/ui/FixedViewPagerAdapter.java b/src/com/android/messaging/ui/FixedViewPagerAdapter.java
deleted file mode 100644
index bd73b71..0000000
--- a/src/com/android/messaging/ui/FixedViewPagerAdapter.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.v4.view.PagerAdapter;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * A PagerAdapter that provides a fixed number of paged Views provided by a fixed set of
- * {@link PagerViewHolder}'s. This allows us to put a fixed number of Views, instead of fragments,
- * into a given ViewPager.
- */
-public class FixedViewPagerAdapter<T extends PagerViewHolder> extends PagerAdapter {
- private final T[] mViewHolders;
-
- public FixedViewPagerAdapter(final T[] viewHolders) {
- Assert.notNull(viewHolders);
- mViewHolders = viewHolders;
- }
-
- @Override
- public Object instantiateItem(final ViewGroup container, final int position) {
- final PagerViewHolder viewHolder = getViewHolder(position);
- final View view = viewHolder.getView(container);
- if (view == null) {
- return null;
- }
- view.setTag(viewHolder);
- container.addView(view);
- return viewHolder;
- }
-
- @Override
- public void destroyItem(final ViewGroup container, final int position, final Object object) {
- final PagerViewHolder viewHolder = getViewHolder(position);
- final View destroyedView = viewHolder.destroyView();
- if (destroyedView != null) {
- container.removeView(destroyedView);
- }
- }
-
- @Override
- public int getCount() {
- return mViewHolders.length;
- }
-
- @Override
- public boolean isViewFromObject(final View view, final Object object) {
- return view.getTag() == object;
- }
-
- public T getViewHolder(final int i) {
- return getViewHolder(i, true /* rtlAware */);
- }
-
- @VisibleForTesting
- public T getViewHolder(final int i, final boolean rtlAware) {
- return mViewHolders[rtlAware ? getRtlPosition(i) : i];
- }
-
- @Override
- public Parcelable saveState() {
- // The paged views in the view pager gets created and destroyed as the user scrolls through
- // them. By default, only the pages to the immediate left and right of the current visible
- // page are realized. Moreover, if the activity gets destroyed and recreated, the pages are
- // automatically destroyed. Therefore, in order to preserve transient page UI states that
- // are not persisted in the DB we'd like to store them in a Bundle when views get
- // destroyed. When the views get recreated, we rehydrate them by passing them the saved
- // data. When the activity gets destroyed, it invokes saveState() on this adapter to
- // add this saved Bundle to the overall saved instance state.
- final Bundle savedViewHolderState = new Bundle(Factory.get().getApplicationContext()
- .getClassLoader());
- for (int i = 0; i < mViewHolders.length; i++) {
- final Parcelable pageState = getViewHolder(i).saveState();
- savedViewHolderState.putParcelable(getInstanceStateKeyForPage(i), pageState);
- }
- return savedViewHolderState;
- }
-
- @Override
- public void restoreState(final Parcelable state, final ClassLoader loader) {
- if (state instanceof Bundle) {
- final Bundle restoredViewHolderState = (Bundle) state;
- ((Bundle) state).setClassLoader(Factory.get().getApplicationContext().getClassLoader());
- for (int i = 0; i < mViewHolders.length; i++) {
- final Parcelable pageState = restoredViewHolderState
- .getParcelable(getInstanceStateKeyForPage(i));
- getViewHolder(i).restoreState(pageState);
- }
- } else {
- super.restoreState(state, loader);
- }
- }
-
- public void resetState() {
- for (int i = 0; i < mViewHolders.length; i++) {
- getViewHolder(i).resetState();
- }
- }
-
- private String getInstanceStateKeyForPage(final int i) {
- return getViewHolder(i).getClass().getCanonicalName() + "_savedstate_" + i;
- }
-
- protected int getRtlPosition(final int position) {
- if (UiUtils.isRtlMode()) {
- return mViewHolders.length - 1 - position;
- }
- return position;
- }
-}
diff --git a/src/com/android/messaging/ui/ImeDetectFrameLayout.java b/src/com/android/messaging/ui/ImeDetectFrameLayout.java
deleted file mode 100644
index 32564ea..0000000
--- a/src/com/android/messaging/ui/ImeDetectFrameLayout.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-
-public class ImeDetectFrameLayout extends FrameLayout {
- public ImeDetectFrameLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int measuredHeight = getMeasuredHeight();
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, "ImeDetectFrameLayout " +
- "measuredHeight: " + measuredHeight + " getMeasuredHeight(): " +
- getMeasuredHeight());
- }
-
- if (measuredHeight != getMeasuredHeight() && getContext() instanceof ImeUtil.ImeStateHost) {
- ((ImeUtil.ImeStateHost) getContext()).onDisplayHeightChanged(heightMeasureSpec);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/LicenseActivity.java b/src/com/android/messaging/ui/LicenseActivity.java
deleted file mode 100644
index a28da81..0000000
--- a/src/com/android/messaging/ui/LicenseActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.webkit.WebView;
-
-import com.android.messaging.R;
-
-public class LicenseActivity extends Activity {
- private final String LICENSE_URL = "file:///android_asset/licenses.html";
-
- @Override
- public void onCreate(final Bundle bundle) {
- super.onCreate(bundle);
- setContentView(R.layout.license_activity);
- final WebView webView = (WebView) findViewById(R.id.content);
- webView.loadUrl(LICENSE_URL);
- }
-}
diff --git a/src/com/android/messaging/ui/LineWrapLayout.java b/src/com/android/messaging/ui/LineWrapLayout.java
deleted file mode 100644
index 5219811..0000000
--- a/src/com/android/messaging/ui/LineWrapLayout.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-
-/**
-* A line-wrapping flow layout. Arranges children in horizontal flow, packing as many
-* child views as possible on each line. When the current line does not
-* have enough horizontal space, the layout continues on the next line.
-*/
-public class LineWrapLayout extends ViewGroup {
- public LineWrapLayout(Context context) {
- this(context, null);
- }
-
- public LineWrapLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int startPadding = UiUtils.getPaddingStart(this);
- final int endPadding = UiUtils.getPaddingEnd(this);
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- final int widthSize = MeasureSpec.getSize(widthMeasureSpec) - startPadding - endPadding;
- final boolean isFixedSize = (widthMode == MeasureSpec.EXACTLY);
-
- int height = 0;
-
- int childCount = getChildCount();
- int childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
-
- int x = startPadding;
- int currLineWidth = 0;
- int currLineHeight = 0;
- int maxLineWidth = 0;
-
- for (int i = 0; i < childCount; i++) {
- View currChild = getChildAt(i);
- if (currChild.getVisibility() == GONE) {
- continue;
- }
- LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
- int startMargin = layoutParams.getStartMargin();
- int endMargin = layoutParams.getEndMargin();
- currChild.measure(childWidthSpec, MeasureSpec.UNSPECIFIED);
- int childMeasuredWidth = currChild.getMeasuredWidth() + startMargin + endMargin;
- int childMeasuredHeight = currChild.getMeasuredHeight() + layoutParams.topMargin +
- layoutParams.bottomMargin;
-
- if ((x + childMeasuredWidth) > widthSize) {
- // New line. Update the overall height and reset trackers.
- height += currLineHeight;
- currLineHeight = 0;
- x = startPadding;
- currLineWidth = 0;
- startMargin = 0;
- }
-
- x += childMeasuredWidth;
- currLineWidth += childMeasuredWidth;
- currLineHeight = Math.max(currLineHeight, childMeasuredHeight);
- maxLineWidth = Math.max(currLineWidth, maxLineWidth);
- }
- // And account for the height of the last line.
- height += currLineHeight;
-
- int width = isFixedSize ? widthSize : maxLineWidth;
- setMeasuredDimension(width + startPadding + endPadding,
- height + getPaddingTop() + getPaddingBottom());
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- final int startPadding = UiUtils.getPaddingStart(this);
- final int endPadding = UiUtils.getPaddingEnd(this);
- int width = getWidth() - startPadding - endPadding;
- int y = getPaddingTop();
- int x = startPadding;
- int childCount = getChildCount();
-
- int currLineHeight = 0;
-
- // Do a dry-run first to get the line heights.
- final ArrayList<Integer> lineHeights = new ArrayList<Integer>();
- for (int i = 0; i < childCount; i++) {
- View currChild = getChildAt(i);
- if (currChild.getVisibility() == GONE) {
- continue;
- }
- LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
- int childWidth = currChild.getMeasuredWidth();
- int childHeight = currChild.getMeasuredHeight();
- int startMargin = layoutParams.getStartMargin();
- int endMargin = layoutParams.getEndMargin();
-
- if ((x + childWidth + startMargin + endMargin) > width) {
- // new line
- lineHeights.add(currLineHeight);
- currLineHeight = 0;
- x = startPadding;
- startMargin = 0;
- }
- currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin +
- layoutParams.bottomMargin);
- x += childWidth + startMargin + endMargin;
- }
- // Add the last line height.
- lineHeights.add(currLineHeight);
-
- // Now perform the actual layout.
- x = startPadding;
- currLineHeight = 0;
- int lineIndex = 0;
- for (int i = 0; i < childCount; i++) {
- View currChild = getChildAt(i);
- if (currChild.getVisibility() == GONE) {
- continue;
- }
- LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
- int childWidth = currChild.getMeasuredWidth();
- int childHeight = currChild.getMeasuredHeight();
- int startMargin = layoutParams.getStartMargin();
- int endMargin = layoutParams.getEndMargin();
-
- if ((x + childWidth + startMargin + endMargin) > width) {
- // new line
- y += currLineHeight;
- currLineHeight = 0;
- x = startPadding;
- startMargin = 0;
- lineIndex++;
- }
- final int startPositionX = x + startMargin;
- int startPositionY = y + layoutParams.topMargin; // default to top gravity
- final int majorGravity = layoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK;
- if (majorGravity != Gravity.TOP && lineHeights.size() > lineIndex) {
- final int lineHeight = lineHeights.get(lineIndex);
- switch (majorGravity) {
- case Gravity.BOTTOM:
- startPositionY = y + lineHeight - childHeight - layoutParams.bottomMargin;
- break;
-
- case Gravity.CENTER_VERTICAL:
- startPositionY = y + (lineHeight - childHeight) / 2;
- break;
- }
- }
-
- if (OsUtil.isAtLeastJB_MR2() && getResources().getConfiguration()
- .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- currChild.layout(width - startPositionX - childWidth, startPositionY,
- width - startPositionX, startPositionY + childHeight);
- } else {
- currChild.layout(startPositionX, startPositionY, startPositionX + childWidth,
- startPositionY + childHeight);
- }
- currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin +
- layoutParams.bottomMargin);
- x += childWidth + startMargin + endMargin;
- }
- }
-
- @Override
- protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- }
-
- public static final class LayoutParams extends FrameLayout.LayoutParams {
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
- }
-
- public LayoutParams(int width, int height) {
- super(width, height);
- }
-
- public LayoutParams(ViewGroup.LayoutParams source) {
- super(source);
- }
-
- public int getStartMargin() {
- if (OsUtil.isAtLeastJB_MR2()) {
- return getMarginStart();
- } else {
- return leftMargin;
- }
- }
-
- public int getEndMargin() {
- if (OsUtil.isAtLeastJB_MR2()) {
- return getMarginEnd();
- } else {
- return rightMargin;
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/ListEmptyView.java b/src/com/android/messaging/ui/ListEmptyView.java
deleted file mode 100644
index 8cf3049..0000000
--- a/src/com/android/messaging/ui/ListEmptyView.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.LinearLayout.LayoutParams;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-
-/**
- * A common reusable view that shows a hint image and text for an empty list view.
- */
-public class ListEmptyView extends LinearLayout {
- private ImageView mEmptyImageHint;
- private TextView mEmptyTextHint;
-
- public ListEmptyView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mEmptyImageHint = (ImageView) findViewById(R.id.empty_image_hint);
- mEmptyTextHint = (TextView) findViewById(R.id.empty_text_hint);
- }
-
- public void setImageHint(final int resId) {
- mEmptyImageHint.setImageResource(resId);
- }
-
- public void setTextHint(final int resId) {
- mEmptyTextHint.setText(getResources().getText(resId));
- }
-
- public void setTextHint(final CharSequence hintText) {
- mEmptyTextHint.setText(hintText);
- }
-
- public void setIsImageVisible(final boolean isImageVisible) {
- mEmptyImageHint.setVisibility(isImageVisible ? VISIBLE : GONE);
- }
-
- public void setIsVerticallyCentered(final boolean isVerticallyCentered) {
- int gravity =
- isVerticallyCentered ? Gravity.CENTER : Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- ((LinearLayout.LayoutParams) mEmptyImageHint.getLayoutParams()).gravity = gravity;
- ((LinearLayout.LayoutParams) mEmptyTextHint.getLayoutParams()).gravity = gravity;
- getLayoutParams().height =
- isVerticallyCentered ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
- requestLayout();
- }
-}
diff --git a/src/com/android/messaging/ui/MaxHeightScrollView.java b/src/com/android/messaging/ui/MaxHeightScrollView.java
deleted file mode 100644
index a000cd1..0000000
--- a/src/com/android/messaging/ui/MaxHeightScrollView.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.util.AttributeSet;
-import android.widget.ScrollView;
-
-import com.android.messaging.R;
-
-/**
- * A ScrollView that limits the maximum height that it can take. This is to work around android
- * layout's limitation of not having android:maxHeight.
- */
-public class MaxHeightScrollView extends ScrollView {
- private static final int NO_MAX_HEIGHT = -1;
-
- private final int mMaxHeight;
-
- public MaxHeightScrollView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- final TypedArray attr = context.obtainStyledAttributes(attrs,
- R.styleable.MaxHeightScrollView, 0, 0);
- mMaxHeight = attr.getDimensionPixelSize(R.styleable.MaxHeightScrollView_android_maxHeight,
- NO_MAX_HEIGHT);
- attr.recycle();
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mMaxHeight != NO_MAX_HEIGHT) {
- setMeasuredDimension(getMeasuredWidth(), Math.min(getMeasuredHeight(), mMaxHeight));
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/MultiAttachmentLayout.java b/src/com/android/messaging/ui/MultiAttachmentLayout.java
deleted file mode 100644
index f620245..0000000
--- a/src/com/android/messaging/ui/MultiAttachmentLayout.java
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AnimationSet;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.TranslateAnimation;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Holds and displays multiple attachments in a 4x2 grid. Each preview image "tile" can take
- * one of three sizes - small (1x1), wide (2x1) and large (2x2). We have a number of predefined
- * layout settings designed for holding 2, 3, 4+ attachments (these layout settings are
- * tweakable by design request to allow for max flexibility). For a visual example, consider the
- * following attachment layout:
- *
- * +---------------+----------------+
- * | | |
- * | | B |
- * | | |
- * | A |-------+--------|
- * | | | |
- * | | C | D |
- * | | | |
- * +---------------+-------+--------+
- *
- * In the above example, the layout consists of four tiles, A-D. A is a large tile, B is a
- * wide tile and C & D are both small tiles. A starts at (0,0) and ends at (1,1), B starts at
- * (2,0) and ends at (3,0), and so on. In our layout class we'd have these tiles in the order
- * of A-D, so that we make sure the last tile is always the one where we can put the overflow
- * indicator (e.g. "+2").
- */
-public class MultiAttachmentLayout extends FrameLayout {
-
- public interface OnAttachmentClickListener {
- boolean onAttachmentClick(MessagePartData attachment, Rect viewBoundsOnScreen,
- boolean longPress);
- }
-
- private static final int GRID_WIDTH = 4; // in # of cells
- private static final int GRID_HEIGHT = 2; // in # of cells
-
- /**
- * Represents a preview image tile in the layout
- */
- private static class Tile {
- public final int startX;
- public final int startY;
- public final int endX;
- public final int endY;
-
- private Tile(final int startX, final int startY, final int endX, final int endY) {
- this.startX = startX;
- this.startY = startY;
- this.endX = endX;
- this.endY = endY;
- }
-
- public int getWidthMeasureSpec(final int cellWidth, final int padding) {
- return MeasureSpec.makeMeasureSpec((endX - startX + 1) * cellWidth - padding * 2,
- MeasureSpec.EXACTLY);
- }
-
- public int getHeightMeasureSpec(final int cellHeight, final int padding) {
- return MeasureSpec.makeMeasureSpec((endY - startY + 1) * cellHeight - padding * 2,
- MeasureSpec.EXACTLY);
- }
-
- public static Tile large(final int startX, final int startY) {
- return new Tile(startX, startY, startX + 1, startY + 1);
- }
-
- public static Tile wide(final int startX, final int startY) {
- return new Tile(startX, startY, startX + 1, startY);
- }
-
- public static Tile small(final int startX, final int startY) {
- return new Tile(startX, startY, startX, startY);
- }
- }
-
- /**
- * A layout simply contains a list of tiles, in the order of top-left -> bottom-right.
- */
- private static class Layout {
- public final List<Tile> tiles;
- public Layout(final Tile[] tilesArray) {
- tiles = Arrays.asList(tilesArray);
- }
- }
-
- /**
- * List of predefined layout configurations w.r.t no. of attachments.
- */
- private static final Layout[] ATTACHMENT_LAYOUTS_BY_COUNT = {
- null, // Doesn't support zero attachments.
- null, // Doesn't support one attachment. Single attachment preview is used instead.
- new Layout(new Tile[] { Tile.large(0, 0), Tile.large(2, 0) }), // 2 items
- new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.wide(2, 1) }), // 3 items
- new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.small(2, 1), // 4+ items
- Tile.small(3, 1) }),
- };
-
- /**
- * List of predefined RTL layout configurations w.r.t no. of attachments.
- */
- private static final Layout[] ATTACHMENT_RTL_LAYOUTS_BY_COUNT = {
- null, // Doesn't support zero attachments.
- null, // Doesn't support one attachment. Single attachment preview is used instead.
- new Layout(new Tile[] { Tile.large(2, 0), Tile.large(0, 0)}), // 2 items
- new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.wide(0, 1) }), // 3 items
- new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.small(1, 1), // 4+ items
- Tile.small(0, 1) }),
- };
-
- private Layout mCurrentLayout;
- private ArrayList<ViewWrapper> mPreviewViews;
- private int mPlusNumber;
- private TextView mPlusTextView;
- private OnAttachmentClickListener mAttachmentClickListener;
- private AsyncImageViewDelayLoader mImageViewDelayLoader;
-
- public MultiAttachmentLayout(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mPreviewViews = new ArrayList<ViewWrapper>();
- }
-
- public void bindAttachments(final Iterable<MessagePartData> attachments,
- final Rect transitionRect, final int count) {
- final ArrayList<ViewWrapper> previousViews = mPreviewViews;
- mPreviewViews = new ArrayList<ViewWrapper>();
- removeView(mPlusTextView);
- mPlusTextView = null;
-
- determineLayout(attachments, count);
- buildViews(attachments, previousViews, transitionRect);
-
- // Remove all previous views that couldn't be recycled.
- for (final ViewWrapper viewWrapper : previousViews) {
- removeView(viewWrapper.view);
- }
- requestLayout();
- }
-
- public OnAttachmentClickListener getOnAttachmentClickListener() {
- return mAttachmentClickListener;
- }
-
- public void setOnAttachmentClickListener(final OnAttachmentClickListener listener) {
- mAttachmentClickListener = listener;
- }
-
- public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
- mImageViewDelayLoader = delayLoader;
- }
-
- public void setColorFilter(int color) {
- for (ViewWrapper viewWrapper : mPreviewViews) {
- if (viewWrapper.view instanceof AsyncImageView) {
- ((AsyncImageView) viewWrapper.view).setColorFilter(color);
- }
- }
- }
-
- public void clearColorFilter() {
- for (ViewWrapper viewWrapper : mPreviewViews) {
- if (viewWrapper.view instanceof AsyncImageView) {
- ((AsyncImageView) viewWrapper.view).clearColorFilter();
- }
- }
- }
-
- private void determineLayout(final Iterable<MessagePartData> attachments, final int count) {
- Assert.isTrue(attachments != null);
- final boolean isRtl = AccessibilityUtil.isLayoutRtl(getRootView());
- if (isRtl) {
- mCurrentLayout = ATTACHMENT_RTL_LAYOUTS_BY_COUNT[Math.min(count,
- ATTACHMENT_RTL_LAYOUTS_BY_COUNT.length - 1)];
- } else {
- mCurrentLayout = ATTACHMENT_LAYOUTS_BY_COUNT[Math.min(count,
- ATTACHMENT_LAYOUTS_BY_COUNT.length - 1)];
- }
-
- // We must have a valid layout for the current configuration.
- Assert.notNull(mCurrentLayout);
-
- mPlusNumber = count - mCurrentLayout.tiles.size();
- Assert.isTrue(mPlusNumber >= 0);
- }
-
- private void buildViews(final Iterable<MessagePartData> attachments,
- final ArrayList<ViewWrapper> previousViews, final Rect transitionRect) {
- final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
- final int count = mCurrentLayout.tiles.size();
- int i = 0;
- final Iterator<MessagePartData> iterator = attachments.iterator();
- while (iterator.hasNext() && i < count) {
- final MessagePartData attachment = iterator.next();
- ViewWrapper attachmentWrapper = null;
- // Try to recycle a previous view first
- for (int j = 0; j < previousViews.size(); j++) {
- final ViewWrapper previousView = previousViews.get(j);
- if (previousView.attachment.equals(attachment) &&
- !(previousView.attachment instanceof PendingAttachmentData)) {
- attachmentWrapper = previousView;
- previousViews.remove(j);
- break;
- }
- }
-
- if (attachmentWrapper == null) {
- final View view = AttachmentPreviewFactory.createAttachmentPreview(layoutInflater,
- attachment, this, AttachmentPreviewFactory.TYPE_MULTIPLE,
- false /* startImageRequest */, mAttachmentClickListener);
-
- if (view == null) {
- // createAttachmentPreview can return null if something goes wrong (e.g.
- // attachment has unsupported contentType)
- continue;
- }
- if (view instanceof AsyncImageView && mImageViewDelayLoader != null) {
- AsyncImageView asyncImageView = (AsyncImageView) view;
- asyncImageView.setDelayLoader(mImageViewDelayLoader);
- }
- addView(view);
- attachmentWrapper = new ViewWrapper(view, attachment);
- // Help animate from single to multi by copying over the prev location
- if (count == 2 && i == 1 && transitionRect != null) {
- attachmentWrapper.prevLeft = transitionRect.left;
- attachmentWrapper.prevTop = transitionRect.top;
- attachmentWrapper.prevWidth = transitionRect.width();
- attachmentWrapper.prevHeight = transitionRect.height();
- }
- }
- i++;
- Assert.notNull(attachmentWrapper);
- mPreviewViews.add(attachmentWrapper);
-
- // The first view will animate in using PopupTransitionAnimation, but the remaining
- // views will slide from their previous position to their new position within the
- // layout
- if (i == 0) {
- AttachmentPreview.tryAnimateViewIn(attachment, attachmentWrapper.view);
- }
- attachmentWrapper.needsSlideAnimation = i > 0;
- }
-
- // Build the plus text view (e.g. "+2") for when there are more attachments than what
- // this layout can display.
- if (mPlusNumber > 0) {
- mPlusTextView = (TextView) layoutInflater.inflate(R.layout.attachment_more_text_view,
- null /* parent */);
- mPlusTextView.setText(getResources().getString(R.string.attachment_more_items,
- mPlusNumber));
- addView(mPlusTextView);
- }
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- final int maxWidth = getResources().getDimensionPixelSize(
- R.dimen.multiple_attachment_preview_width);
- final int maxHeight = getResources().getDimensionPixelSize(
- R.dimen.multiple_attachment_preview_height);
- final int width = Math.min(MeasureSpec.getSize(widthMeasureSpec), maxWidth);
- final int height = maxHeight;
- final int cellWidth = width / GRID_WIDTH;
- final int cellHeight = height / GRID_HEIGHT;
- final int count = mPreviewViews.size();
- final int padding = getResources().getDimensionPixelOffset(
- R.dimen.multiple_attachment_preview_padding);
- for (int i = 0; i < count; i++) {
- final View view = mPreviewViews.get(i).view;
- final Tile imageTile = mCurrentLayout.tiles.get(i);
- view.measure(imageTile.getWidthMeasureSpec(cellWidth, padding),
- imageTile.getHeightMeasureSpec(cellHeight, padding));
-
- // Now that we know the size, we can request an appropriately-sized image.
- if (view instanceof AsyncImageView) {
- final ImageRequestDescriptor imageRequest =
- AttachmentPreviewFactory.getImageRequestDescriptorForAttachment(
- mPreviewViews.get(i).attachment,
- view.getMeasuredWidth(),
- view.getMeasuredHeight());
- ((AsyncImageView) view).setImageResourceId(imageRequest);
- }
-
- if (i == count - 1 && mPlusTextView != null) {
- // The plus text view always covers the last attachment.
- mPlusTextView.measure(imageTile.getWidthMeasureSpec(cellWidth, padding),
- imageTile.getHeightMeasureSpec(cellHeight, padding));
- }
- }
- setMeasuredDimension(width, height);
- }
-
- @Override
- protected void onLayout(final boolean changed, final int left, final int top, final int right,
- final int bottom) {
- final int cellWidth = getMeasuredWidth() / GRID_WIDTH;
- final int cellHeight = getMeasuredHeight() / GRID_HEIGHT;
- final int padding = getResources().getDimensionPixelOffset(
- R.dimen.multiple_attachment_preview_padding);
- final int count = mPreviewViews.size();
- for (int i = 0; i < count; i++) {
- final ViewWrapper viewWrapper = mPreviewViews.get(i);
- final View view = viewWrapper.view;
- final Tile imageTile = mCurrentLayout.tiles.get(i);
- final int tileLeft = imageTile.startX * cellWidth;
- final int tileTop = imageTile.startY * cellHeight;
- view.layout(tileLeft + padding, tileTop + padding,
- tileLeft + view.getMeasuredWidth(),
- tileTop + view.getMeasuredHeight());
- if (viewWrapper.needsSlideAnimation) {
- trySlideAttachmentView(viewWrapper);
- viewWrapper.needsSlideAnimation = false;
- } else {
- viewWrapper.prevLeft = view.getLeft();
- viewWrapper.prevTop = view.getTop();
- viewWrapper.prevWidth = view.getWidth();
- viewWrapper.prevHeight = view.getHeight();
- }
-
- if (i == count - 1 && mPlusTextView != null) {
- // The plus text view always covers the last attachment.
- mPlusTextView.layout(tileLeft + padding, tileTop + padding,
- tileLeft + mPlusTextView.getMeasuredWidth(),
- tileTop + mPlusTextView.getMeasuredHeight());
- }
- }
- }
-
- private void trySlideAttachmentView(final ViewWrapper viewWrapper) {
- if (!(viewWrapper.attachment instanceof MediaPickerMessagePartData)) {
- return;
- }
- final View view = viewWrapper.view;
-
-
- final int xOffset = viewWrapper.prevLeft - view.getLeft();
- final int yOffset = viewWrapper.prevTop - view.getTop();
- final float scaleX = viewWrapper.prevWidth / (float) view.getWidth();
- final float scaleY = viewWrapper.prevHeight / (float) view.getHeight();
-
- if (xOffset == 0 && yOffset == 0 && scaleX == 1 && scaleY == 1) {
- // Layout hasn't changed
- return;
- }
-
- final AnimationSet animationSet = new AnimationSet(
- true /* shareInterpolator */);
- animationSet.addAnimation(new TranslateAnimation(xOffset, 0, yOffset, 0));
- animationSet.addAnimation(new ScaleAnimation(scaleX, 1, scaleY, 1));
- animationSet.setDuration(
- UiUtils.MEDIAPICKER_TRANSITION_DURATION);
- animationSet.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR);
- view.startAnimation(animationSet);
- view.invalidate();
- viewWrapper.prevLeft = view.getLeft();
- viewWrapper.prevTop = view.getTop();
- viewWrapper.prevWidth = view.getWidth();
- viewWrapper.prevHeight = view.getHeight();
- }
-
- public View findViewForAttachment(final MessagePartData attachment) {
- for (ViewWrapper wrapper : mPreviewViews) {
- if (wrapper.attachment.equals(attachment) &&
- !(wrapper.attachment instanceof PendingAttachmentData)) {
- return wrapper.view;
- }
- }
- return null;
- }
-
- private static class ViewWrapper {
- final View view;
- final MessagePartData attachment;
- boolean needsSlideAnimation;
- int prevLeft;
- int prevTop;
- int prevWidth;
- int prevHeight;
-
- ViewWrapper(final View view, final MessagePartData attachment) {
- this.view = view;
- this.attachment = attachment;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/OrientedBitmapDrawable.java b/src/com/android/messaging/ui/OrientedBitmapDrawable.java
deleted file mode 100644
index 9242668..0000000
--- a/src/com/android/messaging/ui/OrientedBitmapDrawable.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.view.Gravity;
-
-import com.android.messaging.util.exif.ExifInterface;
-
-/**
- * A drawable that draws a bitmap in a flipped or rotated orientation without having to adjust the
- * bitmap
- */
-public class OrientedBitmapDrawable extends BitmapDrawable {
- private final ExifInterface.OrientationParams mOrientationParams;
- private final Rect mDstRect;
- private int mCenterX;
- private int mCenterY;
- private boolean mApplyGravity;
-
- public static BitmapDrawable create(final int orientation, Resources res, Bitmap bitmap) {
- if (orientation <= ExifInterface.Orientation.TOP_LEFT) {
- // No need to adjust the bitmap, so just use a regular BitmapDrawable
- return new BitmapDrawable(res, bitmap);
- } else {
- // Create an oriented bitmap drawable
- return new OrientedBitmapDrawable(orientation, res, bitmap);
- }
- }
-
- private OrientedBitmapDrawable(final int orientation, Resources res, Bitmap bitmap) {
- super(res, bitmap);
- mOrientationParams = ExifInterface.getOrientationParams(orientation);
- mApplyGravity = true;
- mDstRect = new Rect();
- }
-
- @Override
- public int getIntrinsicWidth() {
- if (mOrientationParams.invertDimensions) {
- return super.getIntrinsicHeight();
- }
- return super.getIntrinsicWidth();
- }
-
- @Override
- public int getIntrinsicHeight() {
- if (mOrientationParams.invertDimensions) {
- return super.getIntrinsicWidth();
- }
- return super.getIntrinsicHeight();
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- mApplyGravity = true;
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mApplyGravity) {
- Gravity.apply(getGravity(), getIntrinsicWidth(), getIntrinsicHeight(), getBounds(),
- mDstRect);
- mCenterX = mDstRect.centerX();
- mCenterY = mDstRect.centerY();
- if (mOrientationParams.invertDimensions) {
- final Matrix matrix = new Matrix();
- matrix.setRotate(mOrientationParams.rotation, mCenterX, mCenterY);
- final RectF rotatedRect = new RectF(mDstRect);
- matrix.mapRect(rotatedRect);
- mDstRect.set((int) rotatedRect.left, (int) rotatedRect.top, (int) rotatedRect.right,
- (int) rotatedRect.bottom);
- }
-
- mApplyGravity = false;
- }
- canvas.save();
- canvas.scale(mOrientationParams.scaleX, mOrientationParams.scaleY, mCenterX, mCenterY);
- canvas.rotate(mOrientationParams.rotation, mCenterX, mCenterY);
- canvas.drawBitmap(getBitmap(), (Rect) null, mDstRect, getPaint());
- canvas.restore();
- }
-}
diff --git a/src/com/android/messaging/ui/PagerViewHolder.java b/src/com/android/messaging/ui/PagerViewHolder.java
deleted file mode 100644
index 2f33a0f..0000000
--- a/src/com/android/messaging/ui/PagerViewHolder.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Holds reusable View(s) for a {@link FixedViewPagerAdapter} to display a page. By using
- * reusable Views inside ViewPagers this allows us to get rid of nested fragments and the messy
- * activity lifecycle problems they entail.
- */
-public interface PagerViewHolder extends PersistentInstanceState {
- /** Instructs the pager to clean up any view related resources
- * @return the destroyed View so that the adapter may remove it from the container, or
- * null if no View has been created. */
- View destroyView();
-
- /** @return The view that presents the page view to the user */
- View getView(ViewGroup container);
-}
diff --git a/src/com/android/messaging/ui/PagingAwareViewPager.java b/src/com/android/messaging/ui/PagingAwareViewPager.java
deleted file mode 100644
index cc7b2cd..0000000
--- a/src/com/android/messaging/ui/PagingAwareViewPager.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-
-import com.android.messaging.util.UiUtils;
-
-/**
- * A simple extension on the standard ViewPager which lets you turn paging on/off.
- */
-public class PagingAwareViewPager extends ViewPager {
- private boolean mPagingEnabled = true;
-
- public PagingAwareViewPager(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setCurrentItem(int item, boolean smoothScroll) {
- super.setCurrentItem(getRtlPosition(item), smoothScroll);
- }
-
- @Override
- public void setCurrentItem(int item) {
- super.setCurrentItem(getRtlPosition(item));
- }
-
- @Override
- public int getCurrentItem() {
- int position = super.getCurrentItem();
- return getRtlPosition(position);
- }
-
- /**
- * Switches position in pager to be adjusted for if we are in RtL mode
- *
- * @param position
- * @return position adjusted if in rtl mode
- */
- protected int getRtlPosition(final int position) {
- final PagerAdapter adapter = getAdapter();
- if (adapter != null && UiUtils.isRtlMode()) {
- return adapter.getCount() - 1 - position;
- }
- return position;
- }
-
- @Override
- public boolean onTouchEvent(final MotionEvent event) {
- if (!mPagingEnabled) {
- return false;
- }
- return super.onTouchEvent(event);
- }
-
- @Override
- public boolean onInterceptTouchEvent(final MotionEvent event) {
- if (!mPagingEnabled) {
- return false;
- }
- return super.onInterceptTouchEvent(event);
- }
-
- public void setPagingEnabled(final boolean enabled) {
- this.mPagingEnabled = enabled;
- }
-
- /** This prevents touch-less scrolling eg. while doing accessibility navigation. */
- @Override
- public boolean canScrollHorizontally(int direction) {
- if (mPagingEnabled) {
- return super.canScrollHorizontally(direction);
- } else {
- return false;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/PermissionCheckActivity.java b/src/com/android/messaging/ui/PermissionCheckActivity.java
deleted file mode 100644
index e992a10..0000000
--- a/src/com/android/messaging/ui/PermissionCheckActivity.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Activity to check if the user has required permissions. If not, it will try to prompt the user
- * to grant permissions. However, the OS may not actually prompt the user if the user had
- * previously checked the "Never ask again" checkbox while denying the required permissions.
- */
-public class PermissionCheckActivity extends Activity {
- private static final int REQUIRED_PERMISSIONS_REQUEST_CODE = 1;
- private static final long AUTOMATED_RESULT_THRESHOLD_MILLLIS = 250;
- private static final String PACKAGE_URI_PREFIX = "package:";
- private long mRequestTimeMillis;
- private TextView mNextView;
- private TextView mSettingsView;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (redirectIfNeeded()) {
- return;
- }
-
- setContentView(R.layout.permission_check_activity);
- UiUtils.setStatusBarColor(this, getColor(R.color.permission_check_activity_background));
-
- findViewById(R.id.exit).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- finish();
- }
- });
-
- mNextView = (TextView) findViewById(R.id.next);
- mNextView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- tryRequestPermission();
- }
- });
-
- mSettingsView = (TextView) findViewById(R.id.settings);
- mSettingsView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.parse(PACKAGE_URI_PREFIX + getPackageName()));
- startActivity(intent);
- }
- });
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (redirectIfNeeded()) {
- return;
- }
- }
-
- private void tryRequestPermission() {
- final String[] missingPermissions = OsUtil.getMissingRequiredPermissions();
- if (missingPermissions.length == 0) {
- redirect();
- return;
- }
-
- mRequestTimeMillis = SystemClock.elapsedRealtime();
- requestPermissions(missingPermissions, REQUIRED_PERMISSIONS_REQUEST_CODE);
- }
-
- @Override
- public void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (requestCode == REQUIRED_PERMISSIONS_REQUEST_CODE) {
- // We do not use grantResults as some of the granted permissions might have been
- // revoked while the permissions dialog box was being shown for the missing permissions.
- if (OsUtil.hasRequiredPermissions()) {
- Factory.get().onRequiredPermissionsAcquired();
- redirect();
- } else {
- final long currentTimeMillis = SystemClock.elapsedRealtime();
- // If the permission request completes very quickly, it must be because the system
- // automatically denied. This can happen if the user had previously denied it
- // and checked the "Never ask again" check box.
- if ((currentTimeMillis - mRequestTimeMillis) < AUTOMATED_RESULT_THRESHOLD_MILLLIS) {
- mNextView.setVisibility(View.GONE);
-
- mSettingsView.setVisibility(View.VISIBLE);
- findViewById(R.id.enable_permission_procedure).setVisibility(View.VISIBLE);
- }
- }
- }
- }
-
- /** Returns true if the redirecting was performed */
- private boolean redirectIfNeeded() {
- if (!OsUtil.hasRequiredPermissions()) {
- return false;
- }
-
- redirect();
- return true;
- }
-
- private void redirect() {
- UIIntents.get().launchConversationListActivity(this);
- finish();
- }
-}
diff --git a/src/com/android/messaging/ui/PersistentInstanceState.java b/src/com/android/messaging/ui/PersistentInstanceState.java
deleted file mode 100644
index 3cc1856..0000000
--- a/src/com/android/messaging/ui/PersistentInstanceState.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.os.Parcelable;
-
-/**
- * Wraps around functionality to save, restore and reset a particular UI component's state.
- */
-public interface PersistentInstanceState {
- /**
- * Saves necessary information about the current state of the instance to later restore
- * the instance to its original state.
- */
- Parcelable saveState();
-
- /**
- * Given a previously saved instance state, attempt to restore to its original view state.
- */
- void restoreState(Parcelable restoredState);
-
- /**
- * Called when any current/preserved state should be reset to zero state.
- */
- void resetState();
-}
diff --git a/src/com/android/messaging/ui/PersonItemView.java b/src/com/android/messaging/ui/PersonItemView.java
deleted file mode 100644
index afd1a99..0000000
--- a/src/com/android/messaging/ui/PersonItemView.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.DetachableBinding;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Shows a view for a "person" - could be a contact or a participant. This always shows a
- * contact icon on the left, and the person's display name on the right.
- *
- * This view is always bound to an abstract PersonItemData class, so to use it for a specific
- * scenario, all you need to do is to create a concrete PersonItemData subclass that bridges
- * between the underlying data (e.g. ParticipantData) and what the UI wants (e.g. display name).
- */
-public class PersonItemView extends LinearLayout implements PersonItemDataListener,
- OnLayoutChangeListener {
- public interface PersonItemViewListener {
- void onPersonClicked(PersonItemData data);
- boolean onPersonLongClicked(PersonItemData data);
- }
-
- protected final DetachableBinding<PersonItemData> mBinding;
- private TextView mNameTextView;
- private TextView mDetailsTextView;
- private ContactIconView mContactIconView;
- private View mDetailsContainer;
- private PersonItemViewListener mListener;
- private boolean mAvatarOnly;
-
- public PersonItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mBinding = BindingBase.createDetachableBinding(this);
- LayoutInflater.from(getContext()).inflate(R.layout.person_item_view, this, true);
- }
-
- @Override
- protected void onFinishInflate() {
- mNameTextView = (TextView) findViewById(R.id.name);
- mDetailsTextView = (TextView) findViewById(R.id.details);
- mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
- mDetailsContainer = findViewById(R.id.details_container);
- mNameTextView.addOnLayoutChangeListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mBinding.isBound()) {
- mBinding.detach();
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mBinding.reAttachIfPossible();
- }
-
- /**
- * Binds to a person item data which will provide us info to be displayed.
- * @param personData the PersonItemData to be bound to.
- */
- public void bind(final PersonItemData personData) {
- if (mBinding.isBound()) {
- if (mBinding.getData().equals(personData)) {
- // Don't rebind if we are requesting the same data.
- return;
- }
- mBinding.unbind();
- }
-
- if (personData != null) {
- mBinding.bind(personData);
- mBinding.getData().setListener(this);
-
- // Accessibility reason : in case phone numbers are mixed in the display name,
- // we need to vocalize it for talkback.
- final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
- getResources(), getDisplayName());
- mNameTextView.setContentDescription(vocalizedDisplayName);
- }
- updateViewAppearance();
- }
-
- /**
- * @return Display name, possibly comma-ellipsized.
- */
- private String getDisplayName() {
- final int width = mNameTextView.getMeasuredWidth();
- final String displayName = mBinding.getData().getDisplayName();
- if (width == 0 || TextUtils.isEmpty(displayName) || !displayName.contains(",")) {
- return displayName;
- }
- final String plusOneString = getContext().getString(R.string.plus_one);
- final String plusNString = getContext().getString(R.string.plus_n);
- return BidiFormatter.getInstance().unicodeWrap(
- UiUtils.commaEllipsize(
- displayName,
- mNameTextView.getPaint(),
- width,
- plusOneString,
- plusNString).toString(),
- TextDirectionHeuristicsCompat.LTR);
- }
-
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- if (mBinding.isBound() && v == mNameTextView) {
- setNameTextView();
- }
- }
-
- /**
- * When set to true, we display only the avatar of the person and hide everything else.
- */
- public void setAvatarOnly(final boolean avatarOnly) {
- mAvatarOnly = avatarOnly;
- mDetailsContainer.setVisibility(avatarOnly ? GONE : VISIBLE);
- }
-
- public boolean isAvatarOnly() {
- return mAvatarOnly;
- }
-
- public void setNameTextColor(final int color) {
- mNameTextView.setTextColor(color);
- }
-
- public void setDetailsTextColor(final int color) {
- mDetailsTextView.setTextColor(color);
- }
-
- public void setListener(final PersonItemViewListener listener) {
- mListener = listener;
- if (mListener == null) {
- return;
- }
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- if (mListener != null && mBinding.isBound()) {
- mListener.onPersonClicked(mBinding.getData());
- }
- }
- });
- final OnLongClickListener onLongClickListener = new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mListener != null && mBinding.isBound()) {
- return mListener.onPersonLongClicked(mBinding.getData());
- }
- return false;
- }
- };
- setOnLongClickListener(onLongClickListener);
- mContactIconView.setOnLongClickListener(onLongClickListener);
- }
-
- public void performClickOnAvatar() {
- mContactIconView.performClick();
- }
-
- protected void updateViewAppearance() {
- if (mBinding.isBound()) {
- setNameTextView();
-
- final String details = mBinding.getData().getDetails();
- if (TextUtils.isEmpty(details)) {
- mDetailsTextView.setVisibility(GONE);
- } else {
- mDetailsTextView.setVisibility(VISIBLE);
- mDetailsTextView.setText(details);
- }
-
- mContactIconView.setImageResourceUri(mBinding.getData().getAvatarUri(),
- mBinding.getData().getContactId(), mBinding.getData().getLookupKey(),
- mBinding.getData().getNormalizedDestination());
- } else {
- mNameTextView.setText("");
- mContactIconView.setImageResourceUri(null);
- }
- }
-
- private void setNameTextView() {
- final String displayName = getDisplayName();
- if (TextUtils.isEmpty(displayName)) {
- mNameTextView.setVisibility(GONE);
- } else {
- mNameTextView.setVisibility(VISIBLE);
- mNameTextView.setText(displayName);
- }
- }
-
- @Override
- public void onPersonDataUpdated(final PersonItemData data) {
- mBinding.ensureBound(data);
- updateViewAppearance();
- }
-
- @Override
- public void onPersonDataFailed(final PersonItemData data, final Exception exception) {
- mBinding.ensureBound(data);
- updateViewAppearance();
- }
-
- public Intent getClickIntent() {
- return mBinding.getData().getClickIntent();
- }
-}
diff --git a/src/com/android/messaging/ui/PlaceholderInsetDrawable.java b/src/com/android/messaging/ui/PlaceholderInsetDrawable.java
deleted file mode 100644
index dda2e7b..0000000
--- a/src/com/android/messaging/ui/PlaceholderInsetDrawable.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
-
-/**
- * A "placeholder" drawable that has the same sizing properties as the real UI element it
- * replaces.
- *
- * This is an InsetDrawable that takes a placeholder drawable (an animation list, or simply
- * a color drawable) and place it in the center of the inset drawable that's sized to the
- * requested source width and height of the image that it replaces. Unlike the base
- * InsetDrawable, this implementation returns the true width and height of the real image
- * that it's placeholding, instead of the intrinsic size of the contained drawable, so that
- * when used in an ImageView, it may be positioned/scaled/cropped the same way the real
- * image is.
- */
-public class PlaceholderInsetDrawable extends InsetDrawable {
- // The dimensions of the real image that this drawable is replacing.
- private final int mSourceWidth;
- private final int mSourceHeight;
-
- /**
- * Given a source drawable, wraps it around in this placeholder drawable by placing the
- * drawable at the center of the container if possible (or fill the container if the
- * drawable doesn't have intrinsic size such as color drawable).
- */
- public static PlaceholderInsetDrawable fromDrawable(final Drawable drawable,
- final int sourceWidth, final int sourceHeight) {
- final int drawableWidth = drawable.getIntrinsicWidth();
- final int drawableHeight = drawable.getIntrinsicHeight();
- final int insetHorizontal = drawableWidth < 0 || drawableWidth > sourceWidth ?
- 0 : (sourceWidth - drawableWidth) / 2;
- final int insetVertical = drawableHeight < 0 || drawableHeight > sourceHeight ?
- 0 : (sourceHeight - drawableHeight) / 2;
- return new PlaceholderInsetDrawable(drawable, insetHorizontal, insetVertical,
- insetHorizontal, insetVertical, sourceWidth, sourceHeight);
- }
-
- private PlaceholderInsetDrawable(final Drawable drawable, final int insetLeft,
- final int insetTop, final int insetRight, final int insetBottom,
- final int sourceWidth, final int sourceHeight) {
- super(drawable, insetLeft, insetTop, insetRight, insetBottom);
- mSourceWidth = sourceWidth;
- mSourceHeight = sourceHeight;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mSourceWidth;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mSourceHeight;
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/PlainTextEditText.java b/src/com/android/messaging/ui/PlainTextEditText.java
deleted file mode 100644
index 8d6e784..0000000
--- a/src/com/android/messaging/ui/PlainTextEditText.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.EditText;
-
-/**
- * We want the EditText used in Conversations to convert text to plain text on paste. This
- * conversion would happen anyway on send, so without this class it could appear to the user
- * that we would send e.g. bold or italic formatting, but in the sent message it would just be
- * plain text.
- */
-public class PlainTextEditText extends EditText {
- private static final char OBJECT_UNICODE = '\uFFFC';
-
- public PlainTextEditText(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- // Intercept and modify the paste event. Let everything else through unchanged.
- @Override
- public boolean onTextContextMenuItem(final int id) {
- if (id == android.R.id.paste) {
- // We can use this to know where the text position was originally before we pasted
- final int selectionStartPrePaste = getSelectionStart();
-
- // Let the EditText's normal paste routine fire, then modify the content after.
- // This is simpler than re-implementing the paste logic, which we'd have to do
- // if we want to get the text from the clipboard ourselves and then modify it.
-
- final boolean result = super.onTextContextMenuItem(id);
- CharSequence text = getText();
- int selectionStart = getSelectionStart();
- int selectionEnd = getSelectionEnd();
-
- // There is an option in the Chrome mobile app to copy image; however, instead of the
- // image in the form of the uri, Chrome gives us the html source for the image, which
- // the platform paste code turns into the unicode object character. The below section
- // of code looks for that edge case and replaces it with the url for the image.
- final int startIndex = selectionStart - 1;
- final int pasteStringLength = selectionStart - selectionStartPrePaste;
- // Only going to handle the case where the pasted object is the image
- if (pasteStringLength == 1 && text.charAt(startIndex) == OBJECT_UNICODE) {
- final ClipboardManager clipboard =
- (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- final ClipData clip = clipboard.getPrimaryClip();
- if (clip != null) {
- ClipData.Item item = clip.getItemAt(0);
- StringBuilder sb = new StringBuilder(text);
- final String url = item.getText().toString();
- sb.replace(selectionStartPrePaste, selectionStart, url);
- text = sb.toString();
- selectionStart = selectionStartPrePaste + url.length();
- selectionEnd = selectionStart;
- }
- }
-
- // This removes the formatting due to the conversion to string.
- setText(text.toString(), BufferType.EDITABLE);
-
- // Restore the cursor selection state.
- setSelection(selectionStart, selectionEnd);
- return result;
- } else {
- return super.onTextContextMenuItem(id);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/PlaybackStateView.java b/src/com/android/messaging/ui/PlaybackStateView.java
deleted file mode 100644
index 8d9aac7..0000000
--- a/src/com/android/messaging/ui/PlaybackStateView.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-/**
- * An interface for a UI element ("View") that reflects the playback state of a piece of media
- * content. It needs to support the ability to take common playback commands (play, pause, stop,
- * restart) and reflect the state in UI (through timer or progress bar etc.)
- */
-public interface PlaybackStateView {
- /**
- * Restart the playback.
- */
- void restart();
-
- /**
- * Reset ("stop") the playback to the starting position.
- */
- void reset();
-
- /**
- * Resume the playback, or start it if it hasn't been started yet.
- */
- void resume();
-
- /**
- * Pause the playback.
- */
- void pause();
-}
diff --git a/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java b/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java
deleted file mode 100644
index c731164..0000000
--- a/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.telephony.TelephonyManager;
-
-import com.android.messaging.datamodel.NoConfirmationSmsSendService;
-import com.android.messaging.util.LogUtil;
-
-public class RemoteInputEntrypointActivity extends BaseBugleActivity {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent intent = getIntent();
- if (intent == null) {
- LogUtil.w(TAG, "No intent attached");
- setResult(RESULT_CANCELED);
- finish();
- return;
- }
-
- // Perform some action depending on the intent
- String action = intent.getAction();
- if (Intent.ACTION_SENDTO.equals(action)) {
- // Build and send the intent
- final Intent sendIntent = new Intent(this, NoConfirmationSmsSendService.class);
- sendIntent.setAction(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE);
- sendIntent.putExtras(intent);
- // Wear apparently passes all of its extras via the clip data. Must pass it along.
- sendIntent.setClipData(intent.getClipData());
- startService(sendIntent);
- setResult(RESULT_OK);
- } else {
- LogUtil.w(TAG, "Unrecognized intent action: " + action);
- setResult(RESULT_CANCELED);
- }
- // This activity should never stick around after processing the intent
- finish();
- }
-}
diff --git a/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java b/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java
deleted file mode 100644
index 6b3e84b..0000000
--- a/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.FragmentTransaction;
-import android.os.Bundle;
-
-/**
- * Activity to contain the dialog of warning sms storage low.
- */
-public class SmsStorageLowWarningActivity extends BaseBugleFragmentActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- SmsStorageLowWarningFragment fragment =
- SmsStorageLowWarningFragment.newInstance();
- ft.add(fragment, null/*tag*/);
- ft.commit();
- }
-}
diff --git a/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java b/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java
deleted file mode 100644
index 3ebfdcf..0000000
--- a/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.action.HandleLowStorageAction;
-import com.android.messaging.sms.SmsReleaseStorage;
-import com.android.messaging.sms.SmsReleaseStorage.Duration;
-import com.android.messaging.sms.SmsStorageStatusManager;
-import com.android.messaging.util.Assert;
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * Dialog to show the sms storage low warning
- */
-public class SmsStorageLowWarningFragment extends Fragment {
- private SmsStorageLowWarningFragment() {
- }
-
- public static SmsStorageLowWarningFragment newInstance() {
- return new SmsStorageLowWarningFragment();
- }
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final FragmentTransaction ft = getFragmentManager().beginTransaction();
- final ChooseActionDialogFragment dialog = ChooseActionDialogFragment.newInstance();
- dialog.setTargetFragment(this, 0/*requestCode*/);
- dialog.show(ft, null/*tag*/);
- }
-
- /**
- * Perform confirm action for a specific action
- *
- * @param actionIndex
- */
- private void confirm(final int actionIndex) {
- final FragmentTransaction ft = getFragmentManager().beginTransaction();
- final ConfirmationDialog dialog = ConfirmationDialog.newInstance(actionIndex);
- dialog.setTargetFragment(this, 0/*requestCode*/);
- dialog.show(ft, null/*tag*/);
- }
-
- /**
- * The dialog is cancelled at any step
- */
- private void cancel() {
- getActivity().finish();
- }
-
- /**
- * The dialog to show for user to choose what delete actions to take when storage is low
- */
- private static class ChooseActionDialogFragment extends DialogFragment {
- public static ChooseActionDialogFragment newInstance() {
- return new ChooseActionDialogFragment();
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-
- final LayoutInflater inflater = getActivity().getLayoutInflater();
- final View dialogLayout = inflater.inflate(
- R.layout.sms_storage_low_warning_dialog, null);
- final ListView actionListView = (ListView) dialogLayout.findViewById(
- R.id.free_storage_action_list);
- final List<String> actions = loadFreeStorageActions(getActivity().getResources());
- final ActionListAdapter listAdapter = new ActionListAdapter(getActivity(), actions);
- actionListView.setAdapter(listAdapter);
-
- builder.setTitle(R.string.sms_storage_low_title)
- .setView(dialogLayout)
- .setNegativeButton(R.string.ignore, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
-
- final Dialog dialog = builder.create();
- dialog.setCanceledOnTouchOutside(false);
- return dialog;
- }
-
- @Override
- public void onCancel(final DialogInterface dialog) {
- ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
- }
-
- private class ActionListAdapter extends ArrayAdapter<String> {
- public ActionListAdapter(final Context context, final List<String> actions) {
- super(context, R.layout.sms_free_storage_action_item_view, actions);
- }
-
- @Override
- public View getView(final int position, final View view, final ViewGroup parent) {
- TextView actionItemView;
- if (view == null || !(view instanceof TextView)) {
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- actionItemView = (TextView) inflater.inflate(
- R.layout.sms_free_storage_action_item_view, parent, false);
- } else {
- actionItemView = (TextView) view;
- }
-
- final String action = getItem(position);
- actionItemView.setText(action);
- actionItemView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- dismiss();
- ((SmsStorageLowWarningFragment) getTargetFragment()).confirm(position);
- }
- });
- return actionItemView;
- }
- }
- }
-
- private static final String KEY_ACTION_INDEX = "action_index";
-
- /**
- * The dialog to confirm user's delete action
- */
- private static class ConfirmationDialog extends DialogFragment {
- private Duration mDuration;
- private String mDurationString;
-
- public static ConfirmationDialog newInstance(final int actionIndex) {
- final ConfirmationDialog dialog = new ConfirmationDialog();
- final Bundle args = new Bundle();
- args.putInt(KEY_ACTION_INDEX, actionIndex);
- dialog.setArguments(args);
- return dialog;
- }
-
- @Override
- public void onCancel(final DialogInterface dialog) {
- ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- mDuration = SmsReleaseStorage.parseMessageRetainingDuration();
- mDurationString = SmsReleaseStorage.getMessageRetainingDurationString(mDuration);
-
- final int actionIndex = getArguments().getInt(KEY_ACTION_INDEX);
- if (actionIndex < 0 || actionIndex > 1) {
- return null;
- }
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(R.string.sms_storage_low_title)
- .setMessage(getConfirmDialogMessage(actionIndex))
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- dismiss();
- ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
- }
- })
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- dismiss();
- handleAction(actionIndex);
- getActivity().finish();
- SmsStorageStatusManager.cancelStorageLowNotification();
- }
- });
- return builder.create();
- }
-
- private void handleAction(final int actionIndex) {
- final long durationInMillis =
- SmsReleaseStorage.durationToTimeInMillis(mDuration);
- switch (actionIndex) {
- case 0:
- HandleLowStorageAction.handleDeleteMediaMessages(durationInMillis);
- break;
-
- case 1:
- HandleLowStorageAction.handleDeleteOldMessages(durationInMillis);
- break;
-
- default:
- Assert.fail("Unsupported action");
- break;
- }
- }
-
- /**
- * Get the confirm dialog text for a specific delete action
- * @param index The action index
- * @return
- */
- private String getConfirmDialogMessage(final int index) {
- switch (index) {
- case 0:
- return getString(R.string.delete_all_media_confirmation, mDurationString);
- case 1:
- return getString(R.string.delete_oldest_messages_confirmation, mDurationString);
- case 2:
- return getString(R.string.auto_delete_oldest_messages_confirmation,
- mDurationString);
- }
- throw new IllegalArgumentException(
- "SmsStorageLowWarningFragment: invalid action index " + index);
- }
- }
-
- /**
- * Load the text of delete message actions
- *
- * @param resources
- * @return
- */
- private static List<String> loadFreeStorageActions(final Resources resources) {
- final Duration duration = SmsReleaseStorage.parseMessageRetainingDuration();
- final String durationString = SmsReleaseStorage.getMessageRetainingDurationString(duration);
- final List<String> actions = Lists.newArrayList();
- actions.add(resources.getString(R.string.delete_all_media));
- actions.add(resources.getString(R.string.delete_oldest_messages, durationString));
-
- // TODO: Auto-purging is disabled for Bugle V1.
- // actions.add(resources.getString(R.string.auto_delete_oldest_messages, durationString));
- return actions;
- }
-}
diff --git a/src/com/android/messaging/ui/SnackBar.java b/src/com/android/messaging/ui/SnackBar.java
deleted file mode 100644
index a278040..0000000
--- a/src/com/android/messaging/ui/SnackBar.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SnackBar {
- public static final int LONG_DURATION_IN_MS = 5000;
- public static final int SHORT_DURATION_IN_MS = 1000;
- public static final int MAX_DURATION_IN_MS = 10000;
-
- public interface SnackBarListener {
- void onActionClick();
- }
-
- /**
- * Defines an action to be performed when the user clicks on the action button on the snack bar
- */
- public static class Action {
- private final Runnable mActionRunnable;
- private final String mActionLabel;
-
- public final static int SNACK_BAR_UNDO = 0;
- public final static int SNACK_BAR_RETRY = 1;
-
- private Action(@Nullable Runnable actionRunnable, @Nullable String actionLabel) {
- mActionRunnable = actionRunnable;
- mActionLabel = actionLabel;
- }
-
- Runnable getActionRunnable() {
- return mActionRunnable;
- }
-
- String getActionLabel() {
- return mActionLabel;
- }
-
- public static Action createUndoAction(final Runnable undoRunnable) {
- return createCustomAction(undoRunnable, Factory.get().getApplicationContext()
- .getString(R.string.snack_bar_undo));
- }
-
- public static Action createRetryAction(final Runnable retryRunnable) {
- return createCustomAction(retryRunnable, Factory.get().getApplicationContext()
- .getString(R.string.snack_bar_retry));
- }
-
-
- public static Action createCustomAction(final Runnable runnable, final String actionLabel) {
- return new Action(runnable, actionLabel);
- }
- }
-
- /**
- * Defines the placement of the snack bar (e.g. anchored view, anchor gravity).
- */
- public static class Placement {
- private final View mAnchorView;
- private final boolean mAnchorAbove;
-
- private Placement(@NonNull final View anchorView, final boolean anchorAbove) {
- Assert.notNull(anchorView);
- mAnchorView = anchorView;
- mAnchorAbove = anchorAbove;
- }
-
- public View getAnchorView() {
- return mAnchorView;
- }
-
- public boolean getAnchorAbove() {
- return mAnchorAbove;
- }
-
- /**
- * Anchor the snack bar above the given {@code anchorView}.
- */
- public static Placement above(final View anchorView) {
- return new Placement(anchorView, true);
- }
-
- /**
- * Anchor the snack bar below the given {@code anchorView}.
- */
- public static Placement below(final View anchorView) {
- return new Placement(anchorView, false);
- }
- }
-
- public static class Builder {
- private static final List<SnackBarInteraction> NO_INTERACTIONS =
- new ArrayList<SnackBarInteraction>();
-
- private final Context mContext;
- private final SnackBarManager mSnackBarManager;
-
- private String mSnackBarMessage;
- private int mDuration = LONG_DURATION_IN_MS;
- private List<SnackBarInteraction> mInteractions = NO_INTERACTIONS;
- private Action mAction;
- private Placement mPlacement;
- // The parent view is only used to get a window token and doesn't affect the layout
- private View mParentView;
-
- public Builder(final SnackBarManager snackBarManager, final View parentView) {
- Assert.notNull(snackBarManager);
- Assert.notNull(parentView);
- mSnackBarManager = snackBarManager;
- mContext = parentView.getContext();
- mParentView = parentView;
- }
-
- public Builder setText(final String snackBarMessage) {
- Assert.isTrue(!TextUtils.isEmpty(snackBarMessage));
- mSnackBarMessage = snackBarMessage;
- return this;
- }
-
- public Builder setAction(final Action action) {
- mAction = action;
- return this;
- }
-
- /**
- * Sets the duration to show this toast for in milliseconds.
- */
- public Builder setDuration(final int duration) {
- Assert.isTrue(0 < duration && duration < MAX_DURATION_IN_MS);
- mDuration = duration;
- return this;
- }
-
- /**
- * Sets the components that this toast's animation will interact with. These components may
- * be animated to make room for the toast.
- */
- public Builder withInteractions(final List<SnackBarInteraction> interactions) {
- mInteractions = interactions;
- return this;
- }
-
- /**
- * Place the snack bar with the given placement requirement.
- */
- public Builder withPlacement(final Placement placement) {
- Assert.isNull(mPlacement);
- mPlacement = placement;
- return this;
- }
-
- public SnackBar build() {
- return new SnackBar(this);
- }
-
- public void show() {
- mSnackBarManager.show(build());
- }
- }
-
- private final View mRootView;
- private final Context mContext;
- private final View mSnackBarView;
- private final String mText;
- private final int mDuration;
- private final List<SnackBarInteraction> mInteractions;
- private final Action mAction;
- private final Placement mPlacement;
- private final TextView mActionTextView;
- private final TextView mMessageView;
- private final FrameLayout mMessageWrapper;
- private final View mParentView;
-
- private SnackBarListener mListener;
-
- private SnackBar(final Builder builder) {
- mContext = builder.mContext;
- mRootView = LayoutInflater.from(mContext).inflate(R.layout.snack_bar,
- null /* WindowManager will show this in show() below */);
- mSnackBarView = mRootView.findViewById(R.id.snack_bar);
- mText = builder.mSnackBarMessage;
- mDuration = builder.mDuration;
- mAction = builder.mAction;
- mPlacement = builder.mPlacement;
- mParentView = builder.mParentView;
- if (builder.mInteractions == null) {
- mInteractions = new ArrayList<SnackBarInteraction>();
- } else {
- mInteractions = builder.mInteractions;
- }
-
- mActionTextView = (TextView) mRootView.findViewById(R.id.snack_bar_action);
- mMessageView = (TextView) mRootView.findViewById(R.id.snack_bar_message);
- mMessageWrapper = (FrameLayout) mRootView.findViewById(R.id.snack_bar_message_wrapper);
-
- setUpButton();
- setUpTextLines();
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public View getRootView() {
- return mRootView;
- }
-
- public View getParentView() {
- return mParentView;
- }
-
- public View getSnackBarView() {
- return mSnackBarView;
- }
-
- public String getMessageText() {
- return mText;
- }
-
- public String getActionLabel() {
- if (mAction == null) {
- return null;
- }
- return mAction.getActionLabel();
- }
-
- public int getDuration() {
- return mDuration;
- }
-
- public Placement getPlacement() {
- return mPlacement;
- }
-
- public List<SnackBarInteraction> getInteractions() {
- return mInteractions;
- }
-
- public void setEnabled(final boolean enabled) {
- mActionTextView.setClickable(enabled);
- }
-
- public void setListener(final SnackBarListener snackBarListener) {
- mListener = snackBarListener;
- }
-
- private void setUpButton() {
- if (mAction == null || mAction.getActionRunnable() == null) {
- mActionTextView.setVisibility(View.GONE);
- // In the XML layout we add left/right padding to the button to add space between
- // the message text and the button and on the right side we add padding to put space
- // between the button and the edge of the snack bar. This is so the button can use the
- // padding area as part of it's click target. Since we have no button, we need to put
- // some margin on the right side. While the left margin is already set on the wrapper,
- // we're setting it again to not have to make a special case for RTL.
- final MarginLayoutParams lp = (MarginLayoutParams) mMessageWrapper.getLayoutParams();
- final int leftRightMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.snack_bar_left_right_margin);
- lp.leftMargin = leftRightMargin;
- lp.rightMargin = leftRightMargin;
- mMessageWrapper.setLayoutParams(lp);
- } else {
- mActionTextView.setVisibility(View.VISIBLE);
- mActionTextView.setText(mAction.getActionLabel());
- mActionTextView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mAction.getActionRunnable().run();
- if (mListener != null) {
- mListener.onActionClick();
- }
- }
- });
- }
- }
-
- private void setUpTextLines() {
- if (mText == null) {
- mMessageView.setVisibility(View.GONE);
- } else {
- mMessageView.setVisibility(View.VISIBLE);
- mMessageView.setText(mText);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/SnackBarInteraction.java b/src/com/android/messaging/ui/SnackBarInteraction.java
deleted file mode 100644
index f723caa..0000000
--- a/src/com/android/messaging/ui/SnackBarInteraction.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-
-import com.google.common.base.Preconditions;
-
-/**
- * An interface that defines how a component can be animated with an {@link SnackBar}.
- */
-public interface SnackBarInteraction {
- /**
- * Returns the animator that will be run in reaction to the given SnackBar being shown.
- *
- * Implementations may return null here if it determines that the given SnackBar does not need
- * to animate this component.
- */
- ViewPropertyAnimator animateOnSnackBarShow(SnackBar snackBar);
-
- /**
- * Returns the animator that will be run in reaction to the given SnackBar being dismissed.
- *
- * Implementations may return null here if it determines that the given SnackBar does not need
- * to animate this component.
- */
- ViewPropertyAnimator animateOnSnackBarDismiss(SnackBar snackBar);
-
- /**
- * A basic implementation of {@link SnackBarInteraction} that assumes that the
- * {@link SnackBar} is always shown with {@link Gravity#BOTTOM} and that the provided View will
- * always need to be translated up to make room for the SnackBar.
- */
- public static class BasicSnackBarInteraction implements SnackBarInteraction {
- private final View mView;
-
- public BasicSnackBarInteraction(final View view) {
- mView = Preconditions.checkNotNull(view);
- }
-
- @Override
- public ViewPropertyAnimator animateOnSnackBarShow(final SnackBar snackBar) {
- final View rootView = snackBar.getRootView();
- return mView.animate().translationY(-rootView.getMeasuredHeight());
- }
-
- @Override
- public ViewPropertyAnimator animateOnSnackBarDismiss(final SnackBar snackBar) {
- return mView.animate().translationY(0);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/SnackBarManager.java b/src/com/android/messaging/ui/SnackBarManager.java
deleted file mode 100644
index e107999..0000000
--- a/src/com/android/messaging/ui/SnackBarManager.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.WindowManager;
-import android.widget.PopupWindow;
-import android.widget.PopupWindow.OnDismissListener;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.ui.SnackBar.Placement;
-import com.android.messaging.ui.SnackBar.SnackBarListener;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.TextUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.base.Joiner;
-
-import java.util.List;
-
-public class SnackBarManager {
-
- private static SnackBarManager sInstance;
-
- public static SnackBarManager get() {
- if (sInstance == null) {
- synchronized (SnackBarManager.class) {
- if (sInstance == null) {
- sInstance = new SnackBarManager();
- }
- }
- }
- return sInstance;
- }
-
- private final Runnable mDismissRunnable = new Runnable() {
- @Override
- public void run() {
- dismiss();
- }
- };
-
- private final OnTouchListener mDismissOnTouchListener = new OnTouchListener() {
- @Override
- public boolean onTouch(final View view, final MotionEvent event) {
- // Dismiss the {@link SnackBar} but don't consume the event.
- dismiss();
- return false;
- }
- };
-
- private final SnackBarListener mDismissOnUserTapListener = new SnackBarListener() {
- @Override
- public void onActionClick() {
- dismiss();
- }
- };
-
- private final int mTranslationDurationMs;
- private final Handler mHideHandler;
-
- private SnackBar mCurrentSnackBar;
- private SnackBar mLatestSnackBar;
- private SnackBar mNextSnackBar;
- private boolean mIsCurrentlyDismissing;
- private PopupWindow mPopupWindow;
-
- private SnackBarManager() {
- mTranslationDurationMs = Factory.get().getApplicationContext().getResources().getInteger(
- R.integer.snackbar_translation_duration_ms);
- mHideHandler = new Handler();
- }
-
- public SnackBar getLatestSnackBar() {
- return mLatestSnackBar;
- }
-
- public SnackBar.Builder newBuilder(final View parentView) {
- return new SnackBar.Builder(this, parentView);
- }
-
- /**
- * The given snackBar is not guaranteed to be shown. If the previous snackBar is animating away,
- * and another snackBar is requested to show after this one, this snackBar will be skipped.
- */
- public void show(final SnackBar snackBar) {
- Assert.notNull(snackBar);
-
- if (mCurrentSnackBar != null) {
- LogUtil.d(LogUtil.BUGLE_TAG, "Showing snack bar, but currentSnackBar was not null.");
-
- // Dismiss the current snack bar. That will cause the next snack bar to be shown on
- // completion.
- mNextSnackBar = snackBar;
- mLatestSnackBar = snackBar;
- dismiss();
- return;
- }
-
- mCurrentSnackBar = snackBar;
- mLatestSnackBar = snackBar;
-
- // We want to know when either button was tapped so we can dismiss.
- snackBar.setListener(mDismissOnUserTapListener);
-
- // Cancel previous dismisses & set dismiss for the delay time.
- mHideHandler.removeCallbacks(mDismissRunnable);
- mHideHandler.postDelayed(mDismissRunnable, snackBar.getDuration());
-
- snackBar.setEnabled(false);
-
- // For some reason, the addView function does not respect layoutParams.
- // We need to explicitly set it first here.
- final View rootView = snackBar.getRootView();
-
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, "Showing snack bar: " + snackBar);
- }
- // Measure the snack bar root view so we know how much to translate by.
- measureSnackBar(snackBar);
- mPopupWindow = new PopupWindow(snackBar.getContext());
- mPopupWindow.setWidth(LayoutParams.MATCH_PARENT);
- mPopupWindow.setHeight(LayoutParams.WRAP_CONTENT);
- mPopupWindow.setBackgroundDrawable(null);
- mPopupWindow.setContentView(rootView);
- final Placement placement = snackBar.getPlacement();
- if (placement == null) {
- mPopupWindow.showAtLocation(
- snackBar.getParentView(), Gravity.BOTTOM | Gravity.START,
- 0, getScreenBottomOffset(snackBar));
- } else {
- final View anchorView = placement.getAnchorView();
-
- // You'd expect PopupWindow.showAsDropDown to ensure the popup moves with the anchor
- // view, which it does for scrolling, but not layout changes, so we have to manually
- // update while the snackbar is showing
- final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- mPopupWindow.update(anchorView, 0, getRelativeOffset(snackBar),
- anchorView.getWidth(), LayoutParams.WRAP_CONTENT);
- }
- };
- anchorView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
- mPopupWindow.setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss() {
- anchorView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
- }
- });
- mPopupWindow.showAsDropDown(anchorView, 0, getRelativeOffset(snackBar));
- }
-
-
- // Animate the toast bar into view.
- placeSnackBarOffScreen(snackBar);
- animateSnackBarOnScreen(snackBar).withEndAction(new Runnable() {
- @Override
- public void run() {
- mCurrentSnackBar.setEnabled(true);
- makeCurrentSnackBarDismissibleOnTouch();
- // Fire an accessibility event as needed
- String snackBarText = snackBar.getMessageText();
- if (!TextUtils.isEmpty(snackBarText) &&
- TextUtils.getTrimmedLength(snackBarText) > 0) {
- snackBarText = snackBarText.trim();
- final String snackBarActionText = snackBar.getActionLabel();
- if (!TextUtil.isAllWhitespace(snackBarActionText)) {
- snackBarText = Joiner.on(", ").join(snackBarText, snackBarActionText);
- }
- AccessibilityUtil.announceForAccessibilityCompat(snackBar.getSnackBarView(),
- null /*accessibilityManager*/, snackBarText);
- }
- }
- });
-
- // Animate any interaction views out of the way.
- animateInteractionsOnShow(snackBar);
- }
-
- /**
- * Dismisses the current toast that is showing. If there is a toast waiting to be shown, that
- * toast will be shown when the current one has been dismissed.
- */
- public void dismiss() {
- mHideHandler.removeCallbacks(mDismissRunnable);
-
- if (mCurrentSnackBar == null || mIsCurrentlyDismissing) {
- return;
- }
-
- final SnackBar snackBar = mCurrentSnackBar;
-
- LogUtil.d(LogUtil.BUGLE_TAG, "Dismissing snack bar.");
- mIsCurrentlyDismissing = true;
-
- snackBar.setEnabled(false);
-
- // Animate the toast bar down.
- final View rootView = snackBar.getRootView();
- animateSnackBarOffScreen(snackBar).withEndAction(new Runnable() {
- @Override
- public void run() {
- rootView.setVisibility(View.GONE);
- try {
- mPopupWindow.dismiss();
- } catch (IllegalArgumentException e) {
- // PopupWindow.dismiss() will fire an IllegalArgumentException if the activity
- // has already ended while we were animating
- }
-
- mCurrentSnackBar = null;
- mIsCurrentlyDismissing = false;
-
- // Show the next toast if one is waiting.
- if (mNextSnackBar != null) {
- final SnackBar localNextSnackBar = mNextSnackBar;
- mNextSnackBar = null;
- show(localNextSnackBar);
- }
- }
- });
-
- // Animate any interaction views back.
- animateInteractionsOnDismiss(snackBar);
- }
-
- private void makeCurrentSnackBarDismissibleOnTouch() {
- // Set touching on the entire view, the {@link SnackBar} itself, as
- // well as the button's dismiss the toast.
- mCurrentSnackBar.getRootView().setOnTouchListener(mDismissOnTouchListener);
- mCurrentSnackBar.getSnackBarView().setOnTouchListener(mDismissOnTouchListener);
- }
-
- private void measureSnackBar(final SnackBar snackBar) {
- final View rootView = snackBar.getRootView();
- final Point displaySize = new Point();
- getWindowManager(snackBar.getContext()).getDefaultDisplay().getSize(displaySize);
- final int widthSpec = ViewGroup.getChildMeasureSpec(
- MeasureSpec.makeMeasureSpec(displaySize.x, MeasureSpec.EXACTLY),
- 0, LayoutParams.MATCH_PARENT);
- final int heightSpec = ViewGroup.getChildMeasureSpec(
- MeasureSpec.makeMeasureSpec(displaySize.y, MeasureSpec.EXACTLY),
- 0, LayoutParams.WRAP_CONTENT);
- rootView.measure(widthSpec, heightSpec);
- }
-
- private void placeSnackBarOffScreen(final SnackBar snackBar) {
- final View rootView = snackBar.getRootView();
- final View snackBarView = snackBar.getSnackBarView();
- snackBarView.setTranslationY(rootView.getMeasuredHeight());
- }
-
- private ViewPropertyAnimator animateSnackBarOnScreen(final SnackBar snackBar) {
- final View snackBarView = snackBar.getSnackBarView();
- return normalizeAnimator(snackBarView.animate()).translationX(0).translationY(0);
- }
-
- private ViewPropertyAnimator animateSnackBarOffScreen(final SnackBar snackBar) {
- final View rootView = snackBar.getRootView();
- final View snackBarView = snackBar.getSnackBarView();
- return normalizeAnimator(snackBarView.animate()).translationY(rootView.getHeight());
- }
-
- private void animateInteractionsOnShow(final SnackBar snackBar) {
- final List<SnackBarInteraction> interactions = snackBar.getInteractions();
- for (final SnackBarInteraction interaction : interactions) {
- if (interaction != null) {
- final ViewPropertyAnimator animator = interaction.animateOnSnackBarShow(snackBar);
- if (animator != null) {
- normalizeAnimator(animator);
- }
- }
- }
- }
-
- private void animateInteractionsOnDismiss(final SnackBar snackBar) {
- final List<SnackBarInteraction> interactions = snackBar.getInteractions();
- for (final SnackBarInteraction interaction : interactions) {
- if (interaction != null) {
- final ViewPropertyAnimator animator =
- interaction.animateOnSnackBarDismiss(snackBar);
- if (animator != null) {
- normalizeAnimator(animator);
- }
- }
- }
- }
-
- private ViewPropertyAnimator normalizeAnimator(final ViewPropertyAnimator animator) {
- return animator
- .setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
- .setDuration(mTranslationDurationMs);
- }
-
- private WindowManager getWindowManager(final Context context) {
- return (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- }
-
- /**
- * Get the offset from the bottom of the screen where the snack bar should be placed.
- */
- private int getScreenBottomOffset(final SnackBar snackBar) {
- final WindowManager windowManager = getWindowManager(snackBar.getContext());
- final DisplayMetrics displayMetrics = new DisplayMetrics();
- if (OsUtil.isAtLeastL()) {
- windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
- } else {
- windowManager.getDefaultDisplay().getMetrics(displayMetrics);
- }
- final int screenHeight = displayMetrics.heightPixels;
-
- if (OsUtil.isAtLeastL()) {
- // In L, the navigation bar is included in the space for the popup window, so we have to
- // offset by the size of the navigation bar
- final Rect displayRect = new Rect();
- snackBar.getParentView().getRootView().getWindowVisibleDisplayFrame(displayRect);
- return screenHeight - displayRect.bottom;
- }
-
- return 0;
- }
-
- private int getRelativeOffset(final SnackBar snackBar) {
- final Placement placement = snackBar.getPlacement();
- Assert.notNull(placement);
- final View anchorView = placement.getAnchorView();
- if (placement.getAnchorAbove()) {
- return -snackBar.getRootView().getMeasuredHeight() - anchorView.getHeight();
- } else {
- // Use the default dropdown positioning
- return 0;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/TestActivity.java b/src/com/android/messaging/ui/TestActivity.java
deleted file mode 100644
index 1693660..0000000
--- a/src/com/android/messaging/ui/TestActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.util.LogUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * An empty activity that can be used to host a fragment or view for unit testing purposes. Lives in
- * app code vs test code due to requirement of ActivityInstrumentationTestCase2.
- */
-public class TestActivity extends FragmentActivity {
- private FragmentEventListener mFragmentEventListener;
-
- public interface FragmentEventListener {
- public void onAttachFragment(Fragment fragment);
- }
-
- @Override
- protected void onCreate(final Bundle bundle) {
- super.onCreate(bundle);
-
- if (bundle != null) {
- // The test case may have configured the fragment, and recreating the activity will
- // lose that configuration. The real activity is the only activity that would know
- // how to reapply that configuration.
- throw new IllegalStateException("TestActivity cannot get recreated");
- }
-
- // There is a race condition, but this often makes it possible for tests to run with the
- // key guard up
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
- WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
- WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
- WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-
- setContentView(R.layout.test_activity);
- }
-
- @VisibleForTesting
- public void setFragment(final Fragment fragment) {
- LogUtil.i(LogUtil.BUGLE_TAG, "TestActivity.setFragment");
- getFragmentManager()
- .beginTransaction()
- .replace(R.id.test_content, fragment)
- .commit();
- getFragmentManager().executePendingTransactions();
- }
-
- @VisibleForTesting
- public void setView(final View view) {
- LogUtil.i(LogUtil.BUGLE_TAG, "TestActivity.setView");
- ((FrameLayout) findViewById(R.id.test_content)).addView(view);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- if (mFragmentEventListener != null) {
- mFragmentEventListener.onAttachFragment(fragment);
- }
- }
-
- public void setFragmentEventListener(final FragmentEventListener fragmentEventListener) {
- mFragmentEventListener = fragmentEventListener;
- }
-}
diff --git a/src/com/android/messaging/ui/UIIntents.java b/src/com/android/messaging/ui/UIIntents.java
deleted file mode 100644
index e5f8a52..0000000
--- a/src/com/android/messaging/ui/UIIntents.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.PendingIntent;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.ConversationIdSet;
-
-/**
- * A central repository of Intents used to start activities.
- */
-public abstract class UIIntents {
- public static UIIntents get() {
- return Factory.get().getUIIntents();
- }
-
- // Intent extras
- public static final String UI_INTENT_EXTRA_CONVERSATION_ID = "conversation_id";
-
- // Sending draft data (from share intent / message forwarding) to the ConversationActivity.
- public static final String UI_INTENT_EXTRA_DRAFT_DATA = "draft_data";
-
- // The request code for picking image from the Document picker.
- public static final int REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER = 1400;
-
- // Indicates what type of notification this applies to (See BugleNotifications:
- // UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, UPDATE_ALL)
- public static final String UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE = "notifications_update";
-
- // Pass a set of conversation id's.
- public static final String UI_INTENT_EXTRA_CONVERSATION_ID_SET = "conversation_id_set";
-
- // Sending class zero message to its activity
- public static final String UI_INTENT_EXTRA_MESSAGE_VALUES = "message_values";
-
- // For the widget to go to the ConversationList from the Conversation.
- public static final String UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST = "goto_conv_list";
-
- // Indicates whether a conversation is launched with custom transition.
- public static final String UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION = "with_custom_transition";
-
- public static final String ACTION_RESET_NOTIFICATIONS =
- "com.android.messaging.reset_notifications";
-
- // Sending VCard uri to VCard detail activity
- public static final String UI_INTENT_EXTRA_VCARD_URI = "vcard_uri";
-
- public static final String CMAS_COMPONENT = "com.android.cellbroadcastreceiver";
-
- // Intent action for local broadcast receiver for conversation self id change.
- public static final String CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION =
- "conversation_self_id_change";
-
- // Conversation self id
- public static final String UI_INTENT_EXTRA_CONVERSATION_SELF_ID = "conversation_self_id";
-
- // For opening an APN editor on a particular row in the apn database.
- public static final String UI_INTENT_EXTRA_APN_ROW_ID = "apn_row_id";
-
- // Subscription id
- public static final String UI_INTENT_EXTRA_SUB_ID = "sub_id";
-
- // Per-Subscription setting activity title
- public static final String UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE =
- "per_sub_setting_title";
-
- // Is application settings launched as the top level settings activity?
- public static final String UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS = "top_level_settings";
-
- // Sending attachment uri from widget
- public static final String UI_INTENT_EXTRA_ATTACHMENT_URI = "attachment_uri";
-
- // Sending attachment content type from widget
- public static final String UI_INTENT_EXTRA_ATTACHMENT_TYPE = "attachment_type";
-
- public static final String ACTION_WIDGET_CONVERSATION =
- "com.android.messaging.widget_conversation:";
-
- public static final String UI_INTENT_EXTRA_REQUIRES_MMS = "requires_mms";
-
- public static final String UI_INTENT_EXTRA_SELF_ID = "self_id";
-
- // Message position to scroll to.
- public static final String UI_INTENT_EXTRA_MESSAGE_POSITION = "message_position";
-
- /**
- * Launch the permission check activity
- */
- public abstract void launchPermissionCheckActivity(final Context context);
-
- public abstract void launchConversationListActivity(final Context context);
-
- /**
- * Launch an activity to show a conversation. This method by default provides no additional
- * activity options.
- */
- public void launchConversationActivity(final Context context,
- final String conversationId, final MessageData draft) {
- launchConversationActivity(context, conversationId, draft, null,
- false /* withCustomTransition */);
- }
-
- /**
- * Launch an activity to show a conversation.
- */
- public abstract void launchConversationActivity(final Context context,
- final String conversationId, final MessageData draft, final Bundle activityOptions,
- final boolean withCustomTransition);
-
-
- /**
- * Launch an activity to show conversation with conversation list in back stack.
- */
- public abstract void launchConversationActivityWithParentStack(Context context,
- String conversationId, String smsBody);
-
- /**
- * Launch an activity to show a conversation as a new task.
- */
- public abstract void launchConversationActivityNewTask(final Context context,
- final String conversationId);
-
- /**
- * Launch an activity to start a new conversation
- */
- public abstract void launchCreateNewConversationActivity(final Context context,
- final MessageData draft);
-
- /**
- * Launch debug activity to set MMS config options.
- */
- public abstract void launchDebugMmsConfigActivity(final Context context);
-
- /**
- * Launch an activity to change settings.
- */
- public abstract void launchSettingsActivity(final Context context);
-
- /**
- * Launch an activity to add a contact with a given destination.
- */
- public abstract void launchAddContactActivity(final Context context, final String destination);
-
- /**
- * Launch an activity to show the document picker to pick an image.
- * @param fragment the requesting fragment
- */
- public abstract void launchDocumentImagePicker(final Fragment fragment);
-
- /**
- * Launch an activity to show people & options for a given conversation.
- */
- public abstract void launchPeopleAndOptionsActivity(final Activity context,
- final String conversationId);
-
- /**
- * Launch an external activity to handle a phone call
- * @param phoneNumber the phone number to call
- * @param clickPosition is the location tapped to start this launch for transition use
- */
- public abstract void launchPhoneCallActivity(final Context context, final String phoneNumber,
- final Point clickPosition);
-
- /**
- * Launch an activity to show archived conversations.
- */
- public abstract void launchArchivedConversationsActivity(final Context context);
-
- /**
- * Launch an activity to show blocked participants.
- */
- public abstract void launchBlockedParticipantsActivity(final Context context);
-
- /**
- * Launch an activity to show a class zero message
- */
- public abstract void launchClassZeroActivity(Context context, ContentValues messageValues);
-
- /**
- * Launch an activity to let the user forward a message
- */
- public abstract void launchForwardMessageActivity(Context context, MessageData message);
-
- /**
- * Launch an activity to show details for a VCard
- */
- public abstract void launchVCardDetailActivity(Context context, Uri vcardUri);
-
- /**
- * Launch an external activity that handles the intent to add VCard to contacts
- */
- public abstract void launchSaveVCardToContactsActivity(Context context, Uri vcardUri);
-
- /**
- * Launch an activity to let the user select & unselect the list of attachments to send.
- */
- public abstract void launchAttachmentChooserActivity(final Activity activity,
- final String conversationId, final int requestCode);
-
- /**
- * Launch full screen video viewer.
- */
- public abstract void launchFullScreenVideoViewer(Context context, Uri videoUri);
-
- /**
- * Launch full screen photo viewer.
- */
- public abstract void launchFullScreenPhotoViewer(Activity activity, Uri initialPhoto,
- Rect initialPhotoBounds, Uri photosUri);
-
- /**
- * Launch an activity to show general app settings
- * @param topLevel indicates whether the app settings is launched as the top-level settings
- * activity (instead of SettingsActivity which shows a collapsed view of the app
- * settings + one settings item per subscription). This is true when there's only one
- * active SIM in the system so we can show this activity directly.
- */
- public abstract void launchApplicationSettingsActivity(Context context, boolean topLevel);
-
- /**
- * Launch an activity to show per-subscription settings
- */
- public abstract void launchPerSubscriptionSettingsActivity(Context context, int subId,
- String settingTitle);
-
- /**
- * Get a ACTION_VIEW intent
- * @param url display the data in the url to users
- */
- public abstract Intent getViewUrlIntent(final String url);
-
- /**
- * Get an intent to launch the ringtone picker
- * @param title the title to show in the ringtone picker
- * @param existingUri the currently set uri
- * @param defaultUri the default uri if none is currently set
- * @param toneType type of ringtone to pick, maybe any of RingtoneManager.TYPE_*
- */
- public abstract Intent getRingtonePickerIntent(final String title, final Uri existingUri,
- final Uri defaultUri, final int toneType);
-
- /**
- * Get an intent to launch the wireless alert viewer.
- */
- public abstract Intent getWirelessAlertsIntent();
-
- /**
- * Get an intent to launch the dialog for changing the default SMS App.
- */
- public abstract Intent getChangeDefaultSmsAppIntent(final Activity activity);
-
- /**
- * Broadcast conversation self id change so it may be reflected in the message compose UI.
- */
- public abstract void broadcastConversationSelfIdChange(final Context context,
- final String conversationId, final String conversationSelfId);
-
- /**
- * Get a PendingIntent for starting conversation list from notifications.
- */
- public abstract PendingIntent getPendingIntentForConversationListActivity(
- final Context context);
-
- /**
- * Get a PendingIntent for starting conversation list from widget.
- */
- public abstract PendingIntent getWidgetPendingIntentForConversationListActivity(
- final Context context);
-
- /**
- * Get a PendingIntent for showing a conversation from notifications.
- */
- public abstract PendingIntent getPendingIntentForConversationActivity(final Context context,
- final String conversationId, final MessageData draft);
-
- /**
- * Get an Intent for showing a conversation from the widget.
- */
- public abstract Intent getIntentForConversationActivity(final Context context,
- final String conversationId, final MessageData draft);
-
- /**
- * Get a PendingIntent for sending a message to a conversation, without opening the Bugle UI.
- *
- * <p>This is intended to be used by the Android Wear companion app when sending transcribed
- * voice replies.
- */
- public abstract PendingIntent getPendingIntentForSendingMessageToConversation(
- final Context context, final String conversationId, final String selfId,
- final boolean requiresMms, final int requestCode);
-
- /**
- * Get a PendingIntent for clearing notifications.
- *
- * <p>This is intended to be used by notifications.
- */
- public abstract PendingIntent getPendingIntentForClearingNotifications(final Context context,
- final int updateTargets, final ConversationIdSet conversationIdSet,
- final int requestCode);
-
- /**
- * Get a PendingIntent for showing low storage notifications.
- */
- public abstract PendingIntent getPendingIntentForLowStorageNotifications(final Context context);
-
- /**
- * Get a PendingIntent for showing a new message to a secondary user.
- */
- public abstract PendingIntent getPendingIntentForSecondaryUserNewMessageNotification(
- final Context context);
-
- /**
- * Get an intent for showing the APN editor.
- */
- public abstract Intent getApnEditorIntent(final Context context, final String rowId, int subId);
-
- /**
- * Get an intent for showing the APN settings.
- */
- public abstract Intent getApnSettingsIntent(final Context context, final int subId);
-
- /**
- * Get an intent for showing advanced settings.
- */
- public abstract Intent getAdvancedSettingsIntent(final Context context);
-
- /**
- * Get an intent for the LaunchConversationActivity.
- */
- public abstract Intent getLaunchConversationActivityIntent(final Context context);
-
- /**
- * Tell MediaScanner to re-scan the specified volume.
- */
- public abstract void kickMediaScanner(final Context context, final String volume);
-
- /**
- * Launch to browser for a url.
- */
- public abstract void launchBrowserForUrl(final Context context, final String url);
-
- /**
- * Get a PendingIntent for the widget conversation template.
- */
- public abstract PendingIntent getWidgetPendingIntentForConversationActivity(
- final Context context, final String conversationId, final int requestCode);
-
- /**
- * Get a PendingIntent for the conversation widget configuration activity template.
- */
- public abstract PendingIntent getWidgetPendingIntentForConfigurationActivity(
- final Context context, final int appWidgetId);
-
-}
diff --git a/src/com/android/messaging/ui/UIIntentsImpl.java b/src/com/android/messaging/ui/UIIntentsImpl.java
deleted file mode 100644
index b7db719..0000000
--- a/src/com/android/messaging/ui/UIIntentsImpl.java
+++ /dev/null
@@ -1,577 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.content.ActivityNotFoundException;
-import android.content.ClipData;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Intents;
-import android.provider.MediaStore;
-import android.provider.Telephony;
-import android.support.annotation.Nullable;
-import android.support.v4.app.TaskStackBuilder;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-
-import com.android.ex.photo.Intents.PhotoViewIntentBuilder;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.ConversationImagePartsView;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.receiver.NotificationReceiver;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.ui.appsettings.ApnEditorActivity;
-import com.android.messaging.ui.appsettings.ApnSettingsActivity;
-import com.android.messaging.ui.appsettings.ApplicationSettingsActivity;
-import com.android.messaging.ui.appsettings.PerSubscriptionSettingsActivity;
-import com.android.messaging.ui.appsettings.SettingsActivity;
-import com.android.messaging.ui.attachmentchooser.AttachmentChooserActivity;
-import com.android.messaging.ui.conversation.ConversationActivity;
-import com.android.messaging.ui.conversation.LaunchConversationActivity;
-import com.android.messaging.ui.conversationlist.ArchivedConversationListActivity;
-import com.android.messaging.ui.conversationlist.ConversationListActivity;
-import com.android.messaging.ui.conversationlist.ForwardMessageActivity;
-import com.android.messaging.ui.conversationsettings.PeopleAndOptionsActivity;
-import com.android.messaging.ui.debug.DebugMmsConfigActivity;
-import com.android.messaging.ui.photoviewer.BuglePhotoViewActivity;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ConversationIdSet;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-/**
- * A central repository of Intents used to start activities.
- */
-public class UIIntentsImpl extends UIIntents {
- private static final String CELL_BROADCAST_LIST_ACTIVITY =
- "com.android.cellbroadcastreceiver.CellBroadcastListActivity";
- private static final String CALL_TARGET_CLICK_KEY = "touchPoint";
- private static final String CALL_TARGET_CLICK_EXTRA_KEY =
- "android.telecom.extra.OUTGOING_CALL_EXTRAS";
- private static final String MEDIA_SCANNER_CLASS =
- "com.android.providers.media.MediaScannerService";
- private static final String MEDIA_SCANNER_PACKAGE = "com.android.providers.media";
- private static final String MEDIA_SCANNER_SCAN_ACTION = "android.media.IMediaScannerService";
-
- /**
- * Get an intent which takes you to a conversation
- */
- private Intent getConversationActivityIntent(final Context context,
- final String conversationId, final MessageData draft,
- final boolean withCustomTransition) {
- final Intent intent = new Intent(context, ConversationActivity.class);
-
- // Always try to reuse the same ConversationActivity in the current task so that we don't
- // have two conversation activities in the back stack.
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- // Otherwise we're starting a new conversation
- if (conversationId != null) {
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- }
- if (draft != null) {
- intent.putExtra(UI_INTENT_EXTRA_DRAFT_DATA, draft);
-
- // If draft attachments came from an external content provider via a share intent, we
- // need to propagate the URI permissions through to ConversationActivity. This requires
- // putting the URIs into the ClipData (setData also works, but accepts only one URI).
- ClipData clipData = null;
- for (final MessagePartData partData : draft.getParts()) {
- if (partData.isAttachment()) {
- final Uri uri = partData.getContentUri();
- if (clipData == null) {
- clipData = ClipData.newRawUri("Attachments", uri);
- } else {
- clipData.addItem(new ClipData.Item(uri));
- }
- }
- }
- if (clipData != null) {
- intent.setClipData(clipData);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- }
- if (withCustomTransition) {
- intent.putExtra(UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION, true);
- }
-
- if (!(context instanceof Activity)) {
- // If the caller supplies an application context, and not an activity context, we must
- // include this flag
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- return intent;
- }
-
- @Override
- public void launchPermissionCheckActivity(final Context context) {
- final Intent intent = new Intent(context, PermissionCheckActivity.class);
- context.startActivity(intent);
- }
-
- /**
- * Get an intent which takes you to the conversation list
- */
- private Intent getConversationListActivityIntent(final Context context) {
- return new Intent(context, ConversationListActivity.class);
- }
-
- @Override
- public void launchConversationListActivity(final Context context) {
- final Intent intent = getConversationListActivityIntent(context);
- context.startActivity(intent);
- }
-
- /**
- * Get an intent which shows the low storage warning activity.
- */
- private Intent getSmsStorageLowWarningActivityIntent(final Context context) {
- return new Intent(context, SmsStorageLowWarningActivity.class);
- }
-
- @Override
- public void launchConversationActivity(final Context context,
- final String conversationId, final MessageData draft, final Bundle activityOptions,
- final boolean withCustomTransition) {
- Assert.isTrue(!withCustomTransition || activityOptions != null);
- final Intent intent = getConversationActivityIntent(context, conversationId, draft,
- withCustomTransition);
- context.startActivity(intent, activityOptions);
- }
-
- @Override
- public void launchConversationActivityNewTask(
- final Context context, final String conversationId) {
- final Intent intent = getConversationActivityIntent(context, conversationId, null,
- false /* withCustomTransition */);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
- }
-
- @Override
- public void launchConversationActivityWithParentStack(final Context context,
- final String conversationId, final String smsBody) {
- final MessageData messageData = TextUtils.isEmpty(smsBody)
- ? null
- : MessageData.createDraftSmsMessage(conversationId, null, smsBody);
- TaskStackBuilder.create(context)
- .addNextIntentWithParentStack(
- getConversationActivityIntent(context, conversationId, messageData,
- false /* withCustomTransition */))
- .startActivities();
- }
-
- @Override
- public void launchCreateNewConversationActivity(final Context context,
- final MessageData draft) {
- final Intent intent = getConversationActivityIntent(context, null, draft,
- false /* withCustomTransition */);
- context.startActivity(intent);
- }
-
- @Override
- public void launchDebugMmsConfigActivity(final Context context) {
- context.startActivity(new Intent(context, DebugMmsConfigActivity.class));
- }
-
- @Override
- public void launchAddContactActivity(final Context context, final String destination) {
- final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- final String destinationType = MmsSmsUtils.isEmailAddress(destination) ?
- Intents.Insert.EMAIL : Intents.Insert.PHONE;
- intent.setType(Contacts.CONTENT_ITEM_TYPE);
- intent.putExtra(destinationType, destination);
- startExternalActivity(context, intent);
- }
-
- @Override
- public void launchSettingsActivity(final Context context) {
- final Intent intent = new Intent(context, SettingsActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- public void launchArchivedConversationsActivity(final Context context) {
- final Intent intent = new Intent(context, ArchivedConversationListActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- public void launchBlockedParticipantsActivity(final Context context) {
- final Intent intent = new Intent(context, BlockedParticipantsActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- public void launchDocumentImagePicker(final Fragment fragment) {
- final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.putExtra(Intent.EXTRA_MIME_TYPES, MessagePartData.ACCEPTABLE_IMAGE_TYPES);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(ContentType.IMAGE_UNSPECIFIED);
-
- fragment.startActivityForResult(intent, REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER);
- }
-
- @Override
- public void launchPeopleAndOptionsActivity(final Activity activity,
- final String conversationId) {
- final Intent intent = new Intent(activity, PeopleAndOptionsActivity.class);
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- activity.startActivityForResult(intent, 0);
- }
-
- @Override
- public void launchPhoneCallActivity(final Context context, final String phoneNumber,
- final Point clickPosition) {
- final Intent intent = new Intent(Intent.ACTION_CALL,
- Uri.parse(UriUtil.SCHEME_TEL + phoneNumber));
- final Bundle extras = new Bundle();
- extras.putParcelable(CALL_TARGET_CLICK_KEY, clickPosition);
- intent.putExtra(CALL_TARGET_CLICK_EXTRA_KEY, extras);
- startExternalActivity(context, intent);
- }
-
- @Override
- public void launchClassZeroActivity(final Context context, final ContentValues messageValues) {
- final Intent classZeroIntent = new Intent(context, ClassZeroActivity.class)
- .putExtra(UI_INTENT_EXTRA_MESSAGE_VALUES, messageValues)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- context.startActivity(classZeroIntent);
- }
-
- @Override
- public void launchForwardMessageActivity(final Context context, final MessageData message) {
- final Intent forwardMessageIntent = new Intent(context, ForwardMessageActivity.class)
- .putExtra(UI_INTENT_EXTRA_DRAFT_DATA, message);
- context.startActivity(forwardMessageIntent);
- }
-
- @Override
- public void launchVCardDetailActivity(final Context context, final Uri vcardUri) {
- final Intent vcardDetailIntent = new Intent(context, VCardDetailActivity.class)
- .putExtra(UI_INTENT_EXTRA_VCARD_URI, vcardUri);
- context.startActivity(vcardDetailIntent);
- }
-
- @Override
- public void launchSaveVCardToContactsActivity(final Context context, final Uri vcardUri) {
- Assert.isTrue(MediaScratchFileProvider.isMediaScratchSpaceUri(vcardUri));
- final Intent intent = new Intent();
- intent.setAction(Intent.ACTION_VIEW);
- intent.setDataAndType(vcardUri, ContentType.TEXT_VCARD.toLowerCase());
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- startExternalActivity(context, intent);
- }
-
- @Override
- public void launchAttachmentChooserActivity(final Activity activity,
- final String conversationId, final int requestCode) {
- final Intent intent = new Intent(activity, AttachmentChooserActivity.class);
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- activity.startActivityForResult(intent, requestCode);
- }
-
- @Override
- public void launchFullScreenVideoViewer(final Context context, final Uri videoUri) {
- final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // So we don't see "surrounding" images in Gallery
- intent.putExtra("SingleItemOnly", true);
- intent.setDataAndType(videoUri, ContentType.VIDEO_UNSPECIFIED);
- startExternalActivity(context, intent);
- }
-
- @Override
- public void launchFullScreenPhotoViewer(final Activity activity, final Uri initialPhoto,
- final Rect initialPhotoBounds, final Uri photosUri) {
- final PhotoViewIntentBuilder builder =
- com.android.ex.photo.Intents.newPhotoViewIntentBuilder(
- activity, BuglePhotoViewActivity.class);
- builder.setPhotosUri(photosUri.toString());
- builder.setInitialPhotoUri(initialPhoto.toString());
- builder.setProjection(ConversationImagePartsView.PhotoViewQuery.PROJECTION);
-
- // Set the location of the imageView so that the photoviewer can animate from that location
- // to full screen.
- builder.setScaleAnimation(initialPhotoBounds.left, initialPhotoBounds.top,
- initialPhotoBounds.width(), initialPhotoBounds.height());
-
- builder.setDisplayThumbsFullScreen(false);
- builder.setMaxInitialScale(8);
- activity.startActivity(builder.build());
- activity.overridePendingTransition(0, 0);
- }
-
- @Override
- public void launchApplicationSettingsActivity(final Context context, final boolean topLevel) {
- final Intent intent = new Intent(context, ApplicationSettingsActivity.class);
- intent.putExtra(UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, topLevel);
- context.startActivity(intent);
- }
-
- @Override
- public void launchPerSubscriptionSettingsActivity(final Context context, final int subId,
- final String settingTitle) {
- final Intent intent = getPerSubscriptionSettingsIntent(context, subId, settingTitle);
- context.startActivity(intent);
- }
-
- @Override
- public Intent getViewUrlIntent(final String url) {
- final Uri uri = Uri.parse(url);
- return new Intent(Intent.ACTION_VIEW, uri);
- }
-
- @Override
- public void broadcastConversationSelfIdChange(final Context context,
- final String conversationId, final String conversationSelfId) {
- final Intent intent = new Intent(CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION);
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_SELF_ID, conversationSelfId);
- LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
- }
-
- @Override
- public PendingIntent getPendingIntentForConversationListActivity(final Context context) {
- final Intent intent = getConversationListActivityIntent(context);
- return getPendingIntentWithParentStack(context, intent, 0);
- }
-
- @Override
- public PendingIntent getPendingIntentForConversationActivity(final Context context,
- final String conversationId, final MessageData draft) {
- final Intent intent = getConversationActivityIntent(context, conversationId, draft,
- false /* withCustomTransition */);
- // Ensure that the platform doesn't reuse PendingIntents across conversations
- intent.setData(MessagingContentProvider.buildConversationMetadataUri(conversationId));
- return getPendingIntentWithParentStack(context, intent, 0);
- }
-
- @Override
- public Intent getIntentForConversationActivity(final Context context,
- final String conversationId, final MessageData draft) {
- final Intent intent = getConversationActivityIntent(context, conversationId, draft,
- false /* withCustomTransition */);
- return intent;
- }
-
- @Override
- public PendingIntent getPendingIntentForSendingMessageToConversation(final Context context,
- final String conversationId, final String selfId, final boolean requiresMms,
- final int requestCode) {
- final Intent intent = new Intent(context, RemoteInputEntrypointActivity.class);
- intent.setAction(Intent.ACTION_SENDTO);
- // Ensure that the platform doesn't reuse PendingIntents across conversations
- intent.setData(MessagingContentProvider.buildConversationMetadataUri(conversationId));
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_SELF_ID, selfId);
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS, requiresMms);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return getPendingIntentWithParentStack(context, intent, requestCode);
- }
-
- @Override
- public PendingIntent getPendingIntentForClearingNotifications(final Context context,
- final int updateTargets, final ConversationIdSet conversationIdSet,
- final int requestCode) {
- final Intent intent = new Intent(context, NotificationReceiver.class);
- intent.setAction(ACTION_RESET_NOTIFICATIONS);
- intent.putExtra(UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE, updateTargets);
- if (conversationIdSet != null) {
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID_SET,
- conversationIdSet.getDelimitedString());
- }
- return PendingIntent.getBroadcast(context,
- requestCode, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- /**
- * Gets a PendingIntent associated with an Intent to start an Activity. All notifications
- * that starts an Activity must use this method to get a PendingIntent, which achieves two
- * goals:
- * 1. The target activities will be created, with any existing ones destroyed. This ensures
- * we don't end up with multiple instances of ConversationListActivity, for example.
- * 2. The target activity, when launched, will have its backstack correctly constructed so
- * back navigation will work correctly.
- */
- private static PendingIntent getPendingIntentWithParentStack(final Context context,
- final Intent intent, final int requestCode) {
- final TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
- // Adds the back stack for the Intent (plus the Intent itself)
- stackBuilder.addNextIntentWithParentStack(intent);
- final PendingIntent resultPendingIntent =
- stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_UPDATE_CURRENT);
- return resultPendingIntent;
- }
-
- @Override
- public Intent getRingtonePickerIntent(final String title, final Uri existingUri,
- final Uri defaultUri, final int toneType) {
- return new Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
- .putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, toneType)
- .putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, title)
- .putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existingUri)
- .putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultUri);
- }
-
- @Override
- public PendingIntent getPendingIntentForLowStorageNotifications(final Context context) {
- final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
- final Intent conversationListIntent = getConversationListActivityIntent(context);
- taskStackBuilder.addNextIntent(conversationListIntent);
- taskStackBuilder.addNextIntentWithParentStack(
- getSmsStorageLowWarningActivityIntent(context));
-
- return taskStackBuilder.getPendingIntent(
- 0, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- @Override
- public PendingIntent getPendingIntentForSecondaryUserNewMessageNotification(
- final Context context) {
- return getPendingIntentForConversationListActivity(context);
- }
-
- @Override
- public Intent getWirelessAlertsIntent() {
- final Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(new ComponentName(CMAS_COMPONENT, CELL_BROADCAST_LIST_ACTIVITY));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- @Override
- public Intent getApnEditorIntent(final Context context, final String rowId, final int subId) {
- final Intent intent = new Intent(context, ApnEditorActivity.class);
- intent.putExtra(UI_INTENT_EXTRA_APN_ROW_ID, rowId);
- intent.putExtra(UI_INTENT_EXTRA_SUB_ID, subId);
- return intent;
- }
-
- @Override
- public Intent getApnSettingsIntent(final Context context, final int subId) {
- final Intent intent = new Intent(context, ApnSettingsActivity.class)
- .putExtra(UI_INTENT_EXTRA_SUB_ID, subId);
- return intent;
- }
-
- @Override
- public Intent getAdvancedSettingsIntent(final Context context) {
- return getPerSubscriptionSettingsIntent(context, ParticipantData.DEFAULT_SELF_SUB_ID, null);
- }
-
- @Override
- public Intent getChangeDefaultSmsAppIntent(final Activity activity) {
- final Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
- intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, activity.getPackageName());
- return intent;
- }
-
- @Override
- public void launchBrowserForUrl(final Context context, final String url) {
- final Intent intent = getViewUrlIntent(url);
- startExternalActivity(context, intent);
- }
-
- /**
- * Provides a safe way to handle external activities which may not exist.
- */
- private void startExternalActivity(final Context context, final Intent intent) {
- try {
- context.startActivity(intent);
- } catch (final ActivityNotFoundException ex) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex);
- UiUtils.showToastAtBottom(R.string.activity_not_found_message);
- }
- }
-
- private Intent getPerSubscriptionSettingsIntent(final Context context, final int subId,
- @Nullable final String settingTitle) {
- return new Intent(context, PerSubscriptionSettingsActivity.class)
- .putExtra(UI_INTENT_EXTRA_SUB_ID, subId)
- .putExtra(UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE, settingTitle);
- }
-
- @Override
- public Intent getLaunchConversationActivityIntent(final Context context) {
- final Intent intent = new Intent(context, LaunchConversationActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
- return intent;
- }
-
- @Override
- public void kickMediaScanner(final Context context, final String volume) {
- final Intent intent = new Intent(MEDIA_SCANNER_SCAN_ACTION)
- .putExtra(MediaStore.MEDIA_SCANNER_VOLUME, volume)
- .setClassName(MEDIA_SCANNER_PACKAGE, MEDIA_SCANNER_CLASS);
- context.startService(intent);
- }
-
- @Override
- public PendingIntent getWidgetPendingIntentForConversationActivity(final Context context,
- final String conversationId, final int requestCode) {
- final Intent intent = getConversationActivityIntent(context, null, null,
- false /* withCustomTransition */);
- if (conversationId != null) {
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
-
- // Set the action to something unique to this conversation so if someone calls this
- // function again on a different conversation, they'll get a new PendingIntent instead
- // of the old one.
- intent.setAction(ACTION_WIDGET_CONVERSATION + conversationId);
- }
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return getPendingIntentWithParentStack(context, intent, requestCode);
- }
-
- @Override
- public PendingIntent getWidgetPendingIntentForConversationListActivity(
- final Context context) {
- final Intent intent = getConversationListActivityIntent(context);
- return getPendingIntentWithParentStack(context, intent, 0);
- }
-
- @Override
- public PendingIntent getWidgetPendingIntentForConfigurationActivity(final Context context,
- final int appWidgetId) {
- final Intent configureIntent = new Intent(context, WidgetPickConversationActivity.class);
- configureIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- configureIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
- configureIntent.setData(Uri.parse(configureIntent.toUri(Intent.URI_INTENT_SCHEME)));
- configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY);
- return getPendingIntentWithParentStack(context, configureIntent, 0);
- }
-}
diff --git a/src/com/android/messaging/ui/VCardDetailActivity.java b/src/com/android/messaging/ui/VCardDetailActivity.java
deleted file mode 100644
index fecdc34..0000000
--- a/src/com/android/messaging/ui/VCardDetailActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.app.Fragment;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-/**
- * An activity that hosts VCardDetailFragment that shows the content of a VCard that contains one
- * or more contacts.
- */
-public class VCardDetailActivity extends BugleActionBarActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.vcard_detail_activity);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- Assert.isTrue(fragment instanceof VCardDetailFragment);
- final Uri vCardUri = getIntent().getParcelableExtra(UIIntents.UI_INTENT_EXTRA_VCARD_URI);
- Assert.notNull(vCardUri);
- final VCardDetailFragment vCardDetailFragment = (VCardDetailFragment) fragment;
- vCardDetailFragment.setVCardUri(vCardUri);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // Treat the home press as back press so that when we go back to
- // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
- onBackPressed();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/VCardDetailAdapter.java b/src/com/android/messaging/ui/VCardDetailAdapter.java
deleted file mode 100644
index cfdd836..0000000
--- a/src/com/android/messaging/ui/VCardDetailAdapter.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseExpandableListAdapter;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.media.VCardResourceEntry;
-import com.android.messaging.datamodel.media.VCardResourceEntry.VCardResourceEntryDestinationItem;
-
-import java.util.List;
-
-/**
- * Displays a list of expandable contact cards shown in the VCardDetailActivity.
- */
-public class VCardDetailAdapter extends BaseExpandableListAdapter {
- private final List<VCardResourceEntry> mVCards;
- private final LayoutInflater mInflater;
-
- public VCardDetailAdapter(final Context context, final List<VCardResourceEntry> vCards) {
- mVCards = vCards;
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- @Override
- public Object getChild(final int groupPosition, final int childPosition) {
- return mVCards.get(groupPosition).getContactInfo().get(childPosition);
- }
-
- @Override
- public long getChildId(final int groupPosition, final int childPosition) {
- return childPosition;
- }
-
- @Override
- public View getChildView(final int groupPosition, final int childPosition,
- final boolean isLastChild, final View convertView, final ViewGroup parent) {
- PersonItemView v;
- if (convertView == null) {
- v = instantiateView(parent);
- } else {
- v = (PersonItemView) convertView;
- }
-
- final VCardResourceEntryDestinationItem item = (VCardResourceEntryDestinationItem)
- getChild(groupPosition, childPosition);
-
- v.bind(item.getDisplayItem());
- return v;
- }
-
- @Override
- public int getChildrenCount(final int groupPosition) {
- return mVCards.get(groupPosition).getContactInfo().size();
- }
-
- @Override
- public Object getGroup(final int groupPosition) {
- return mVCards.get(groupPosition);
- }
-
- @Override
- public int getGroupCount() {
- return mVCards.size();
- }
-
- @Override
- public long getGroupId(final int groupPosition) {
- return groupPosition;
- }
-
- @Override
- public View getGroupView(final int groupPosition, final boolean isExpanded,
- final View convertView, final ViewGroup parent) {
- PersonItemView v;
- if (convertView == null) {
- v = instantiateView(parent);
- } else {
- v = (PersonItemView) convertView;
- }
-
- final VCardResourceEntry item = (VCardResourceEntry) getGroup(groupPosition);
- v.bind(item.getDisplayItem());
- return v;
- }
-
- @Override
- public boolean isChildSelectable(final int groupPosition, final int childPosition) {
- return true;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- private PersonItemView instantiateView(final ViewGroup parent) {
- final PersonItemView v = (PersonItemView) mInflater.inflate(R.layout.people_list_item_view,
- parent, false);
- v.setClickable(false);
- return v;
- }
-}
diff --git a/src/com/android/messaging/ui/VCardDetailFragment.java b/src/com/android/messaging/ui/VCardDetailFragment.java
deleted file mode 100644
index 1b2b88d..0000000
--- a/src/com/android/messaging/ui/VCardDetailFragment.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.app.Fragment;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroup;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.ExpandableListView.OnChildClickListener;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.datamodel.data.VCardContactItemData;
-import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-/**
- * A fragment that shows the content of a VCard that contains one or more contacts.
- */
-public class VCardDetailFragment extends Fragment implements PersonItemDataListener {
- private final Binding<VCardContactItemData> mBinding =
- BindingBase.createBinding(this);
- private ExpandableListView mListView;
- private VCardDetailAdapter mAdapter;
- private Uri mVCardUri;
-
- /**
- * We need to persist the VCard in the scratch directory before letting the user view it.
- * We save this Uri locally, so that if the user cancels the action and re-perform the add
- * to contacts action we don't have to persist it again.
- */
- private Uri mScratchSpaceUri;
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- Assert.notNull(mVCardUri);
- final View view = inflater.inflate(R.layout.vcard_detail_fragment, container, false);
- mListView = (ExpandableListView) view.findViewById(R.id.list);
- mListView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- mListView.setIndicatorBounds(mListView.getWidth() - getResources()
- .getDimensionPixelSize(R.dimen.vcard_detail_group_indicator_width),
- mListView.getWidth());
- }
- });
- mListView.setOnChildClickListener(new OnChildClickListener() {
- @Override
- public boolean onChildClick(ExpandableListView expandableListView, View clickedView,
- int groupPosition, int childPosition, long childId) {
- if (!(clickedView instanceof PersonItemView)) {
- return false;
- }
- final Intent intent = ((PersonItemView) clickedView).getClickIntent();
- if (intent != null) {
- try {
- startActivity(intent);
- } catch (ActivityNotFoundException e) {
- return false;
- }
- return true;
- }
- return false;
- }
- });
- mBinding.bind(DataModel.get().createVCardContactItemData(getActivity(), mVCardUri));
- mBinding.getData().setListener(this);
- return view;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mBinding.isBound()) {
- mBinding.unbind();
- }
- mListView.setAdapter((ExpandableListAdapter) null);
- }
-
- private boolean shouldShowAddToContactsItem() {
- return mBinding.isBound() && mBinding.getData().hasValidVCard();
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.vcard_detail_fragment_menu, menu);
- final MenuItem addToContactsItem = menu.findItem(R.id.action_add_contact);
- addToContactsItem.setVisible(shouldShowAddToContactsItem());
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_add_contact:
- mBinding.ensureBound();
- final Uri vCardUri = mBinding.getData().getVCardUri();
-
- // We have to do things in the background in case we need to copy the vcard data.
- new SafeAsyncTask<Void, Void, Uri>() {
- @Override
- protected Uri doInBackgroundTimed(final Void... params) {
- // We can't delete the persisted vCard file because we don't know when to
- // delete it, since the app that uses it (contacts, dialer) may start or
- // shut down at any point. Therefore, we rely on the system to clean up
- // the cache directory for us.
- return mScratchSpaceUri != null ? mScratchSpaceUri :
- UriUtil.persistContentToScratchSpace(vCardUri);
- }
-
- @Override
- protected void onPostExecute(final Uri result) {
- if (result != null) {
- mScratchSpaceUri = result;
- if (getActivity() != null) {
- MediaScratchFileProvider.addUriToDisplayNameEntry(
- result, mBinding.getData().getDisplayName());
- UIIntents.get().launchSaveVCardToContactsActivity(getActivity(),
- result);
- }
- }
- }
- }.executeOnThreadPool();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- public void setVCardUri(final Uri vCardUri) {
- Assert.isTrue(!mBinding.isBound());
- mVCardUri = vCardUri;
- }
-
- @Override
- public void onPersonDataUpdated(final PersonItemData data) {
- Assert.isTrue(data instanceof VCardContactItemData);
- mBinding.ensureBound();
- final VCardContactItemData vCardData = (VCardContactItemData) data;
- Assert.isTrue(vCardData.hasValidVCard());
- mAdapter = new VCardDetailAdapter(getActivity(), vCardData.getVCardResource().getVCards());
- mListView.setAdapter(mAdapter);
-
- // Expand the contact card if there's only one contact.
- if (mAdapter.getGroupCount() == 1) {
- mListView.expandGroup(0);
- }
- getActivity().invalidateOptionsMenu();
- }
-
- @Override
- public void onPersonDataFailed(final PersonItemData data, final Exception exception) {
- mBinding.ensureBound();
- UiUtils.showToastAtBottom(R.string.failed_loading_vcard);
- getActivity().finish();
- }
-}
diff --git a/src/com/android/messaging/ui/VideoThumbnailView.java b/src/com/android/messaging/ui/VideoThumbnailView.java
deleted file mode 100644
index 966336e..0000000
--- a/src/com/android/messaging/ui/VideoThumbnailView.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.ImageView.ScaleType;
-import android.widget.VideoView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor;
-import com.android.messaging.datamodel.media.VideoThumbnailRequest;
-import com.android.messaging.util.Assert;
-
-/**
- * View that encapsulates a video preview (either as a thumbnail image, or video player), and the
- * a play button to overlay it. Ensures that the video preview maintains the aspect ratio of the
- * original video while trying to respect minimum width/height and constraining to the available
- * bounds
- */
-public class VideoThumbnailView extends FrameLayout {
- /**
- * When in this mode the VideoThumbnailView is a lightweight AsyncImageView with an ImageButton
- * to play the video. Clicking play will launch a full screen player
- */
- private static final int MODE_IMAGE_THUMBNAIL = 0;
-
- /**
- * When in this mode the VideoThumbnailVideo will include a VideoView, and the play button will
- * play the video inline. When in this mode, the loop and playOnLoad attributes can be applied
- * to auto-play or loop the video.
- */
- private static final int MODE_PLAYABLE_VIDEO = 1;
-
- private final int mMode;
- private final boolean mPlayOnLoad;
- private final boolean mAllowCrop;
- private final VideoView mVideoView;
- private final ImageButton mPlayButton;
- private final AsyncImageView mThumbnailImage;
- private int mVideoWidth;
- private int mVideoHeight;
- private Uri mVideoSource;
- private boolean mAnimating;
- private boolean mVideoLoaded;
-
- public VideoThumbnailView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- final TypedArray typedAttributes =
- context.obtainStyledAttributes(attrs, R.styleable.VideoThumbnailView);
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- inflater.inflate(R.layout.video_thumbnail_view, this, true);
-
- mPlayOnLoad = typedAttributes.getBoolean(R.styleable.VideoThumbnailView_playOnLoad, false);
- final boolean loop =
- typedAttributes.getBoolean(R.styleable.VideoThumbnailView_loop, false);
- mMode = typedAttributes.getInt(R.styleable.VideoThumbnailView_mode, MODE_IMAGE_THUMBNAIL);
- mAllowCrop = typedAttributes.getBoolean(R.styleable.VideoThumbnailView_allowCrop, false);
-
- mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
- mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
-
- if (mMode == MODE_PLAYABLE_VIDEO) {
- mVideoView = new VideoView(context);
- // Video view tries to request focus on start which pulls focus from the user's intended
- // focus when we add this control. Remove focusability to prevent this. The play
- // button can still be focused
- mVideoView.setFocusable(false);
- mVideoView.setFocusableInTouchMode(false);
- mVideoView.clearFocus();
- addView(mVideoView, 0, new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(final MediaPlayer mediaPlayer) {
- mVideoLoaded = true;
- mVideoWidth = mediaPlayer.getVideoWidth();
- mVideoHeight = mediaPlayer.getVideoHeight();
- mediaPlayer.setLooping(loop);
- trySwitchToVideo();
- }
- });
- mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(final MediaPlayer mediaPlayer) {
- mPlayButton.setVisibility(View.VISIBLE);
- }
- });
- mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
- @Override
- public boolean onError(final MediaPlayer mediaPlayer, final int i, final int i2) {
- return true;
- }
- });
- } else {
- mVideoView = null;
- }
-
- mPlayButton = (ImageButton) findViewById(R.id.video_thumbnail_play_button);
- if (loop) {
- mPlayButton.setVisibility(View.GONE);
- } else {
- mPlayButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- if (mVideoSource == null) {
- return;
- }
-
- if (mMode == MODE_PLAYABLE_VIDEO) {
- mVideoView.seekTo(0);
- start();
- } else {
- UIIntents.get().launchFullScreenVideoViewer(getContext(), mVideoSource);
- }
- }
- });
- mPlayButton.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View view) {
- // Button prevents long click from propagating up, do it manually
- VideoThumbnailView.this.performLongClick();
- return true;
- }
- });
- }
-
- mThumbnailImage = (AsyncImageView) findViewById(R.id.video_thumbnail_image);
- if (mAllowCrop) {
- mThumbnailImage.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
- mThumbnailImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
- mThumbnailImage.setScaleType(ScaleType.CENTER_CROP);
- } else {
- // This is the default setting in the layout, so No-op.
- }
- final int maxHeight = typedAttributes.getDimensionPixelSize(
- R.styleable.VideoThumbnailView_android_maxHeight, ImageRequest.UNSPECIFIED_SIZE);
- if (maxHeight != ImageRequest.UNSPECIFIED_SIZE) {
- mThumbnailImage.setMaxHeight(maxHeight);
- mThumbnailImage.setAdjustViewBounds(true);
- }
-
- typedAttributes.recycle();
- }
-
- @Override
- protected void onAnimationStart() {
- super.onAnimationStart();
- mAnimating = true;
- }
-
- @Override
- protected void onAnimationEnd() {
- super.onAnimationEnd();
- mAnimating = false;
- trySwitchToVideo();
- }
-
- private void trySwitchToVideo() {
- if (mAnimating) {
- // Don't start video or hide image until after animation completes
- return;
- }
-
- if (!mVideoLoaded) {
- // Video hasn't loaded, nothing more to do
- return;
- }
-
- if (mPlayOnLoad) {
- start();
- } else {
- mVideoView.seekTo(0);
- }
- }
-
- private boolean hasVideoSize() {
- return mVideoWidth != ImageRequest.UNSPECIFIED_SIZE &&
- mVideoHeight != ImageRequest.UNSPECIFIED_SIZE;
- }
-
- public void start() {
- Assert.equals(MODE_PLAYABLE_VIDEO, mMode);
- mPlayButton.setVisibility(View.GONE);
- mThumbnailImage.setVisibility(View.GONE);
- mVideoView.start();
- }
-
- // TODO: The check could be added to MessagePartData itself so that all users of MessagePartData
- // get the right behavior, instead of requiring all the users to do similar checks.
- private static boolean shouldUseGenericVideoIcon(final boolean incomingMessage) {
- return incomingMessage && !VideoThumbnailRequest.shouldShowIncomingVideoThumbnails();
- }
-
- public void setSource(final MessagePartData part, final boolean incomingMessage) {
- if (part == null) {
- clearSource();
- } else {
- mVideoSource = part.getContentUri();
- if (shouldUseGenericVideoIcon(incomingMessage)) {
- mThumbnailImage.setImageResource(R.drawable.generic_video_icon);
- mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
- mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
- } else {
- mThumbnailImage.setImageResourceId(
- new MessagePartVideoThumbnailRequestDescriptor(part));
- if (mVideoView != null) {
- mVideoView.setVideoURI(mVideoSource);
- }
- mVideoWidth = part.getWidth();
- mVideoHeight = part.getHeight();
- }
- }
- }
-
- public void setSource(final Uri videoSource, final boolean incomingMessage) {
- if (videoSource == null) {
- clearSource();
- } else {
- mVideoSource = videoSource;
- if (shouldUseGenericVideoIcon(incomingMessage)) {
- mThumbnailImage.setImageResource(R.drawable.generic_video_icon);
- mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
- mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
- } else {
- mThumbnailImage.setImageResourceId(
- new MessagePartVideoThumbnailRequestDescriptor(videoSource));
- if (mVideoView != null) {
- mVideoView.setVideoURI(videoSource);
- }
- }
- }
- }
-
- private void clearSource() {
- mVideoSource = null;
- mThumbnailImage.setImageResourceId(null);
- mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
- mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
- if (mVideoView != null) {
- mVideoView.setVideoURI(null);
- }
- }
-
- @Override
- public void setMinimumWidth(final int minWidth) {
- super.setMinimumWidth(minWidth);
- if (mVideoView != null) {
- mVideoView.setMinimumWidth(minWidth);
- }
- }
-
- @Override
- public void setMinimumHeight(final int minHeight) {
- super.setMinimumHeight(minHeight);
- if (mVideoView != null) {
- mVideoView.setMinimumHeight(minHeight);
- }
- }
-
- public void setColorFilter(int color) {
- mThumbnailImage.setColorFilter(color);
- mPlayButton.setColorFilter(color);
- }
-
- public void clearColorFilter() {
- mThumbnailImage.clearColorFilter();
- mPlayButton.clearColorFilter();
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mAllowCrop) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- return;
- }
- int desiredWidth = 1;
- int desiredHeight = 1;
- if (mVideoView != null) {
- mVideoView.measure(widthMeasureSpec, heightMeasureSpec);
- }
- mThumbnailImage.measure(widthMeasureSpec, heightMeasureSpec);
- if (hasVideoSize()) {
- desiredWidth = mVideoWidth;
- desiredHeight = mVideoHeight;
- } else {
- desiredWidth = mThumbnailImage.getMeasuredWidth();
- desiredHeight = mThumbnailImage.getMeasuredHeight();
- }
-
- final int minimumWidth = getMinimumWidth();
- final int minimumHeight = getMinimumHeight();
-
- // Constrain the scale to fit within the supplied size
- final float maxScale = Math.max(
- MeasureSpec.getSize(widthMeasureSpec) / (float) desiredWidth,
- MeasureSpec.getSize(heightMeasureSpec) / (float) desiredHeight);
-
- // Scale up to reach minimum width/height
- final float widthScale = Math.max(1, minimumWidth / (float) desiredWidth);
- final float heightScale = Math.max(1, minimumHeight / (float) desiredHeight);
- final float scale = Math.min(maxScale, Math.max(widthScale, heightScale));
- desiredWidth = (int) (desiredWidth * scale);
- desiredHeight = (int) (desiredHeight * scale);
-
- setMeasuredDimension(desiredWidth, desiredHeight);
- }
-
- @Override
- protected void onLayout(final boolean changed, final int left, final int top, final int right,
- final int bottom) {
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- child.layout(0, 0, right - left, bottom - top);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/ViewPagerTabStrip.java b/src/com/android/messaging/ui/ViewPagerTabStrip.java
deleted file mode 100644
index e088296..0000000
--- a/src/com/android/messaging/ui/ViewPagerTabStrip.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.util.OsUtil;
-
-public class ViewPagerTabStrip extends LinearLayout {
- private int mSelectedUnderlineThickness;
- private final Paint mSelectedUnderlinePaint;
-
- private int mIndexForSelection;
- private float mSelectionOffset;
-
- public ViewPagerTabStrip(Context context) {
- this(context, null);
- }
-
- public ViewPagerTabStrip(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final Resources res = context.getResources();
-
- mSelectedUnderlineThickness =
- res.getDimensionPixelSize(R.dimen.pager_tab_underline_selected);
- int underlineColor = res.getColor(R.color.contact_picker_tab_underline);
- int backgroundColor = res.getColor(R.color.action_bar_background_color);
-
- mSelectedUnderlinePaint = new Paint();
- mSelectedUnderlinePaint.setColor(underlineColor);
-
- setBackgroundColor(backgroundColor);
- setWillNotDraw(false);
- }
-
- /**
- * Notifies this view that view pager has been scrolled. We save the tab index
- * and selection offset for interpolating the position and width of selection
- * underline.
- */
- void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- mIndexForSelection = position;
- mSelectionOffset = positionOffset;
- invalidate();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- int childCount = getChildCount();
-
- // Thick colored underline below the current selection
- if (childCount > 0) {
- View selectedTitle = getChildAt(mIndexForSelection);
- int selectedLeft = selectedTitle.getLeft();
- int selectedRight = selectedTitle.getRight();
- final boolean isRtl = isRtl();
- final boolean hasNextTab = isRtl ? mIndexForSelection > 0
- : (mIndexForSelection < (getChildCount() - 1));
- if ((mSelectionOffset > 0.0f) && hasNextTab) {
- // Draw the selection partway between the tabs
- View nextTitle = getChildAt(mIndexForSelection + (isRtl ? -1 : 1));
- int nextLeft = nextTitle.getLeft();
- int nextRight = nextTitle.getRight();
-
- selectedLeft = (int) (mSelectionOffset * nextLeft +
- (1.0f - mSelectionOffset) * selectedLeft);
- selectedRight = (int) (mSelectionOffset * nextRight +
- (1.0f - mSelectionOffset) * selectedRight);
- }
-
- int height = getHeight();
- canvas.drawRect(selectedLeft, height - mSelectedUnderlineThickness,
- selectedRight, height, mSelectedUnderlinePaint);
- }
- }
-
- private boolean isRtl() {
- return OsUtil.isAtLeastJB_MR2() ? getLayoutDirection() == View.LAYOUT_DIRECTION_RTL : false;
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/ViewPagerTabs.java b/src/com/android/messaging/ui/ViewPagerTabs.java
deleted file mode 100644
index f31a071..0000000
--- a/src/com/android/messaging/ui/ViewPagerTabs.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Outline;
-import android.graphics.drawable.ColorDrawable;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Lightweight implementation of ViewPager tabs. This looks similar to traditional actionBar tabs,
- * but allows for the view containing the tabs to be placed anywhere on screen. Text-related
- * attributes can also be assigned in XML - these will get propogated to the child TextViews
- * automatically.
- *
- * Note: this file is taken from the AOSP /packages/apps/ContactsCommon/src/com/android/contacts/
- * common/list/ViewPagerTabs.java. Some platform specific API calls (e.g. ViewOutlineProvider which
- * assumes L and above) have been modified to support down to Api Level 16.
- */
-public class ViewPagerTabs extends HorizontalScrollView implements ViewPager.OnPageChangeListener {
-
- ViewPager mPager;
- private ViewPagerTabStrip mTabStrip;
-
- /**
- * Linearlayout that will contain the TextViews serving as tabs. This is the only child
- * of the parent HorizontalScrollView.
- */
- final int mTextStyle;
- final ColorStateList mTextColor;
- final int mTextSize;
- final boolean mTextAllCaps;
- int mPrevSelected = -1;
- int mSidePadding;
-
- private static final int TAB_SIDE_PADDING_IN_DPS = 10;
-
- // TODO: This should use <declare-styleable> in the future
- private static final int[] ATTRS = new int[] {
- android.R.attr.textSize,
- android.R.attr.textStyle,
- android.R.attr.textColor,
- android.R.attr.textAllCaps
- };
-
- /**
- * Shows a toast with the tab description when long-clicked.
- */
- private class OnTabLongClickListener implements OnLongClickListener {
- final String mTabDescription;
-
- public OnTabLongClickListener(String tabDescription) {
- mTabDescription = tabDescription;
- }
-
- @Override
- public boolean onLongClick(View v) {
- final int[] screenPos = new int[2];
- getLocationOnScreen(screenPos);
-
- final Context context = getContext();
- final int width = getWidth();
- final int height = getHeight();
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
- Toast toast = Toast.makeText(context, mTabDescription, Toast.LENGTH_SHORT);
-
- // Show the toast under the tab
- toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
- (screenPos[0] + width / 2) - screenWidth / 2, screenPos[1] + height);
-
- toast.show();
- return true;
- }
- }
-
- public ViewPagerTabs(Context context) {
- this(context, null);
- }
-
- public ViewPagerTabs(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ViewPagerTabs(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- setFillViewport(true);
-
- mSidePadding = (int) (getResources().getDisplayMetrics().density * TAB_SIDE_PADDING_IN_DPS);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
- mTextSize = a.getDimensionPixelSize(0, 0);
- mTextStyle = a.getInt(1, 0);
- mTextColor = a.getColorStateList(2);
- mTextAllCaps = a.getBoolean(3, false);
-
- mTabStrip = new ViewPagerTabStrip(context);
- addView(mTabStrip,
- new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
- a.recycle();
-
- // enable shadow casting from view bounds
- if (OsUtil.isAtLeastL()) {
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRect(0, 0, view.getWidth(), view.getHeight());
- }
- });
- }
- }
-
- public void setViewPager(ViewPager viewPager) {
- mPager = viewPager;
- addTabs(mPager.getAdapter());
- }
-
- private void addTabs(PagerAdapter adapter) {
- mTabStrip.removeAllViews();
-
- final int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- addTab(adapter.getPageTitle(i), i);
- }
- }
-
- private void addTab(CharSequence tabTitle, final int position) {
- final TextView textView = new TextView(getContext());
- textView.setText(tabTitle);
- textView.setBackgroundResource(R.drawable.contact_picker_tab_background_selector);
- textView.setGravity(Gravity.CENTER);
- textView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mPager.setCurrentItem(getRtlPosition(position));
- }
- });
-
- // Assign various text appearance related attributes to child views.
- if (mTextStyle > 0) {
- textView.setTypeface(textView.getTypeface(), mTextStyle);
- }
- if (mTextSize > 0) {
- textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
- }
- if (mTextColor != null) {
- textView.setTextColor(mTextColor);
- }
- textView.setAllCaps(mTextAllCaps);
- textView.setPadding(mSidePadding, 0, mSidePadding, 0);
- mTabStrip.addView(textView, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.MATCH_PARENT, 1));
- // Default to the first child being selected
- if (position == 0) {
- mPrevSelected = 0;
- textView.setSelected(true);
- }
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- position = getRtlPosition(position);
- int tabStripChildCount = mTabStrip.getChildCount();
- if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
- return;
- }
-
- mTabStrip.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
-
- @Override
- public void onPageSelected(int position) {
- position = getRtlPosition(position);
- int tabStripChildCount = mTabStrip.getChildCount();
- if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
- return;
- }
-
- if (mPrevSelected >= 0 && mPrevSelected < tabStripChildCount) {
- mTabStrip.getChildAt(mPrevSelected).setSelected(false);
- }
- final View selectedChild = mTabStrip.getChildAt(position);
- selectedChild.setSelected(true);
-
- // Update scroll position
- final int scrollPos = selectedChild.getLeft() - (getWidth() - selectedChild.getWidth()) / 2;
- smoothScrollTo(scrollPos, 0);
- mPrevSelected = position;
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
- }
-
- private int getRtlPosition(int position) {
- if (OsUtil.isAtLeastJB_MR2() && Factory.get().getApplicationContext().getResources()
- .getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- return mTabStrip.getChildCount() - 1 - position;
- }
- return position;
- }
-
- public int getSelectedItemPosition() {
- return mPrevSelected;
- }
-}
diff --git a/src/com/android/messaging/ui/WidgetPickConversationActivity.java b/src/com/android/messaging/ui/WidgetPickConversationActivity.java
deleted file mode 100644
index 60e1318..0000000
--- a/src/com/android/messaging/ui/WidgetPickConversationActivity.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.app.Fragment;
-import android.appwidget.AppWidgetManager;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.ui.conversationlist.ShareIntentFragment;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.widget.WidgetConversationProvider;
-
-public class WidgetPickConversationActivity extends BaseBugleActivity implements
- ShareIntentFragment.HostInterface {
-
- private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-
- // Set the result to CANCELED. This will cause the widget host to cancel
- // out of the widget placement if they press the back button.
- setResult(RESULT_CANCELED);
-
- // Find the widget id from the intent.
- final Intent intent = getIntent();
- final Bundle extras = intent.getExtras();
- if (extras != null) {
- mAppWidgetId = extras.getInt(
- AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- }
-
- // If they gave us an intent without the widget id, just bail.
- if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
- finish();
- }
-
- final ShareIntentFragment convPicker = new ShareIntentFragment();
- final Bundle bundle = new Bundle();
- bundle.putBoolean(ShareIntentFragment.HIDE_NEW_CONVERSATION_BUTTON_KEY, true);
- convPicker.setArguments(bundle);
- convPicker.show(getFragmentManager(), "ShareIntentFragment");
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- final Intent intent = getIntent();
- final String action = intent.getAction();
- if (!AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)) {
- // Unsupported action.
- Assert.fail("Unsupported action type: " + action);
- }
- }
-
- @Override
- public void onConversationClick(final ConversationListItemData conversationListItemData) {
- saveConversationidPref(mAppWidgetId, conversationListItemData.getConversationId());
-
- // Push widget update to surface with newly set prefix
- WidgetConversationProvider.rebuildWidget(this, mAppWidgetId);
-
- // Make sure we pass back the original appWidgetId
- Intent resultValue = new Intent();
- resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
- setResult(RESULT_OK, resultValue);
- finish();
- }
-
- @Override
- public void onCreateConversationClick() {
- // We should never get here because we're hiding the new conversation button in the
- // ShareIntentFragment by setting HIDE_NEW_CONVERSATION_BUTTON_KEY in the arguments.
- finish();
- }
-
- // Write the ConversationId to the SharedPreferences object for this widget
- static void saveConversationidPref(int appWidgetId, String conversationId) {
- final BuglePrefs prefs = Factory.get().getWidgetPrefs();
- prefs.putString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId, conversationId);
- }
-
- // Read the ConversationId from the SharedPreferences object for this widget.
- public static String getConversationIdPref(int appWidgetId) {
- final BuglePrefs prefs = Factory.get().getWidgetPrefs();
- return prefs.getString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId, null);
- }
-
- // Delete the ConversationId preference from the SharedPreferences object for this widget.
- public static void deleteConversationIdPref(int appWidgetId) {
- final BuglePrefs prefs = Factory.get().getWidgetPrefs();
- prefs.remove(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId);
- }
-
-}
diff --git a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java b/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
deleted file mode 100644
index 21529c6..0000000
--- a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.animation;
-
-import android.animation.TypeEvaluator;
-import android.app.Activity;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-import android.widget.PopupWindow;
-
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Animates viewToAnimate from startRect to the place where it is in the layout, viewToAnimate
- * should be in its final destination location before startAfterLayoutComplete is called.
- * viewToAnimate will be drawn scaled and offset in a popupWindow.
- * This class handles the case where the viewToAnimate moves during the animation
- */
-public class PopupTransitionAnimation extends Animation {
- /** The view we're animating */
- private final View mViewToAnimate;
-
- /** The rect to start the slide in animation from */
- private final Rect mStartRect;
-
- /** The rect of the currently animated view */
- private Rect mCurrentRect;
-
- /** The rect that we're animating to. This can change during the animation */
- private final Rect mDestRect;
-
- /** The bounds of the popup in window coordinates. Does not include notification bar */
- private final Rect mPopupRect;
-
- /** The bounds of the action bar in window coordinates. We clip the popup to below this */
- private final Rect mActionBarRect;
-
- /** Interpolates between the start and end rect for every animation tick */
- private final TypeEvaluator<Rect> mRectEvaluator;
-
- /** The popup window that holds contains the animating view */
- private PopupWindow mPopupWindow;
-
- /** The layout root for the popup which is where the animated view is rendered */
- private View mPopupRoot;
-
- /** The action bar's view */
- private final View mActionBarView;
-
- private Runnable mOnStartCallback;
- private Runnable mOnStopCallback;
-
- public PopupTransitionAnimation(final Rect startRect, final View viewToAnimate) {
- mViewToAnimate = viewToAnimate;
- mStartRect = startRect;
- mCurrentRect = new Rect(mStartRect);
- mDestRect = new Rect();
- mPopupRect = new Rect();
- mActionBarRect = new Rect();
- final Activity activity = (Activity) viewToAnimate.getRootView().getContext();
- mActionBarView = activity.getWindow().getDecorView().findViewById(
- android.support.v7.appcompat.R.id.action_bar);
- mRectEvaluator = RectEvaluatorCompat.create();
- setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
- setInterpolator(UiUtils.DEFAULT_INTERPOLATOR);
- setAnimationListener(new AnimationListener() {
- @Override
- public void onAnimationStart(final Animation animation) {
- if (mOnStartCallback != null) {
- mOnStartCallback.run();
- }
- mEvents.append("oAS,");
- }
-
- @Override
- public void onAnimationEnd(final Animation animation) {
- if (mOnStopCallback != null) {
- mOnStopCallback.run();
- }
- dismiss();
- mEvents.append("oAE,");
- }
-
- @Override
- public void onAnimationRepeat(final Animation animation) {
- }
- });
- }
-
- private final StringBuilder mEvents = new StringBuilder();
- private final Runnable mCleanupRunnable = new Runnable() {
- @Override
- public void run() {
- LogUtil.w(LogUtil.BUGLE_TAG, "PopupTransitionAnimation: " + mEvents);
- }
- };
-
- /**
- * Ensures the animation is ready before starting the animation.
- * viewToAnimate must first be layed out so we know where we will animate to
- */
- public void startAfterLayoutComplete() {
- // We want layout to occur, and then we immediately animate it in, so hide it initially to
- // reduce jank on the first frame
- mViewToAnimate.setVisibility(View.INVISIBLE);
- mViewToAnimate.setAlpha(0);
-
- final Runnable startAnimation = new Runnable() {
- boolean mRunComplete = false;
- boolean mFirstTry = true;
-
- @Override
- public void run() {
- if (mRunComplete) {
- return;
- }
-
- mViewToAnimate.getGlobalVisibleRect(mDestRect);
- // In Android views which are visible but haven't computed their size yet have a
- // size of 1x1 because anything with a size of 0x0 is considered hidden. We can't
- // start the animation until after the size is greater than 1x1
- if (mDestRect.width() <= 1 || mDestRect.height() <= 1) {
- // Layout hasn't occurred yet
- if (!mFirstTry) {
- // Give up if this is not the first try, since layout change still doesn't
- // yield a size for the view. This is likely because the media picker is
- // full screen so there's no space left for the animated view. We give up
- // on animation, but need to make sure the view that was initially
- // hidden is re-shown.
- mViewToAnimate.setAlpha(1);
- mViewToAnimate.setVisibility(View.VISIBLE);
- } else {
- mFirstTry = false;
- UiUtils.doOnceAfterLayoutChange(mViewToAnimate, this);
- }
- return;
- }
-
- mRunComplete = true;
- mViewToAnimate.startAnimation(PopupTransitionAnimation.this);
- mViewToAnimate.invalidate();
- // http://b/20856505: The PopupWindow sometimes does not get dismissed.
- ThreadUtil.getMainThreadHandler().postDelayed(mCleanupRunnable, getDuration() * 2);
- }
- };
-
- startAnimation.run();
- }
-
- public PopupTransitionAnimation setOnStartCallback(final Runnable onStart) {
- mOnStartCallback = onStart;
- return this;
- }
-
- public PopupTransitionAnimation setOnStopCallback(final Runnable onStop) {
- mOnStopCallback = onStop;
- return this;
- }
-
- @Override
- protected void applyTransformation(final float interpolatedTime, final Transformation t) {
- if (mPopupWindow == null) {
- initPopupWindow();
- }
- // Update mDestRect as it may have moved during the animation
- mPopupRect.set(UiUtils.getMeasuredBoundsOnScreen(mPopupRoot));
- mActionBarRect.set(UiUtils.getMeasuredBoundsOnScreen(mActionBarView));
- computeDestRect();
-
- // Update currentRect to the new animated coordinates, and request mPopupRoot to redraw
- // itself at the new coordinates
- mCurrentRect = mRectEvaluator.evaluate(interpolatedTime, mStartRect, mDestRect);
- mPopupRoot.invalidate();
-
- if (interpolatedTime >= 0.98) {
- mEvents.append("aT").append(interpolatedTime).append(',');
- }
- if (interpolatedTime == 1) {
- dismiss();
- }
- }
-
- private void dismiss() {
- mEvents.append("d,");
- mViewToAnimate.setAlpha(1);
- mViewToAnimate.setVisibility(View.VISIBLE);
- // Delay dismissing the popup window to let mViewToAnimate draw under it and reduce the
- // flash
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- try {
- mPopupWindow.dismiss();
- } catch (IllegalArgumentException e) {
- // PopupWindow.dismiss() will fire an IllegalArgumentException if the activity
- // has already ended while we were animating
- }
- ThreadUtil.getMainThreadHandler().removeCallbacks(mCleanupRunnable);
- }
- });
- }
-
- @Override
- public boolean willChangeBounds() {
- return false;
- }
-
- /**
- * Computes mDestRect (the position in window space of the placeholder view that we should
- * animate to). Some frames during the animation fail to compute getGlobalVisibleRect, so use
- * the last known values in that case
- */
- private void computeDestRect() {
- final int prevTop = mDestRect.top;
- final int prevLeft = mDestRect.left;
- final int prevRight = mDestRect.right;
- final int prevBottom = mDestRect.bottom;
-
- if (!getViewScreenMeasureRect(mViewToAnimate, mDestRect)) {
- mDestRect.top = prevTop;
- mDestRect.left = prevLeft;
- mDestRect.bottom = prevBottom;
- mDestRect.right = prevRight;
- }
- }
-
- /**
- * Sets up the PopupWindow that the view will animate in. Animating the size and position of a
- * popup can be choppy, so instead we make the popup fill the entire space of the screen, and
- * animate the position of viewToAnimate within the popup using a Transformation
- */
- private void initPopupWindow() {
- mPopupRoot = new View(mViewToAnimate.getContext()) {
- @Override
- protected void onDraw(final Canvas canvas) {
- canvas.save();
- canvas.clipRect(getLeft(), mActionBarRect.bottom - mPopupRect.top, getRight(),
- getBottom());
- canvas.drawColor(Color.TRANSPARENT);
- final float previousAlpha = mViewToAnimate.getAlpha();
- mViewToAnimate.setAlpha(1);
- // The view's global position includes the notification bar height, but
- // the popup window may or may not cover the notification bar (depending on screen
- // rotation, IME status etc.), so we need to compensate for this difference by
- // offseting vertically.
- canvas.translate(mCurrentRect.left, mCurrentRect.top - mPopupRect.top);
-
- final float viewWidth = mViewToAnimate.getWidth();
- final float viewHeight = mViewToAnimate.getHeight();
- if (viewWidth > 0 && viewHeight > 0) {
- canvas.scale(mCurrentRect.width() / viewWidth,
- mCurrentRect.height() / viewHeight);
- }
- canvas.clipRect(0, 0, mCurrentRect.width(), mCurrentRect.height());
- if (!mPopupRect.isEmpty()) {
- // HACK: Layout is unstable until mPopupRect is non-empty.
- mViewToAnimate.draw(canvas);
- }
- mViewToAnimate.setAlpha(previousAlpha);
- canvas.restore();
- }
- };
- mPopupWindow = new PopupWindow(mViewToAnimate.getContext());
- mPopupWindow.setBackgroundDrawable(null);
- mPopupWindow.setContentView(mPopupRoot);
- mPopupWindow.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
- mPopupWindow.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
- mPopupWindow.setTouchable(false);
- // We must pass a non-zero value for the y offset, or else the system resets the status bar
- // color to black (M only) during the animation. The actual position of the window (and
- // the animated view inside it) are still correct, regardless of what we pass for the y
- // parameter (e.g. 1 and 100 both work). Not entirely sure why this works.
- mPopupWindow.showAtLocation(mViewToAnimate, Gravity.TOP, 0, 1);
- }
-
- private static boolean getViewScreenMeasureRect(final View view, final Rect outRect) {
- outRect.set(UiUtils.getMeasuredBoundsOnScreen(view));
- return !outRect.isEmpty();
- }
-}
diff --git a/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java b/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java
deleted file mode 100644
index e3c60fc..0000000
--- a/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.animation;
-
-import android.animation.RectEvaluator;
-import android.animation.TypeEvaluator;
-import android.graphics.Rect;
-
-import com.android.messaging.util.OsUtil;
-
-/**
- * This evaluator can be used to perform type interpolation between <code>Rect</code> values.
- * It's backward compatible to Api Level 11.
- */
-public class RectEvaluatorCompat implements TypeEvaluator<Rect> {
- public static TypeEvaluator<Rect> create() {
- if (OsUtil.isAtLeastJB_MR2()) {
- return new RectEvaluator();
- } else {
- return new RectEvaluatorCompat();
- }
- }
-
- @Override
- public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
- int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction);
- int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction);
- int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction);
- int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction);
- return new Rect(left, top, right, bottom);
- }
-}
diff --git a/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java b/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
deleted file mode 100644
index 6abfdf9..0000000
--- a/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.animation;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.v4.view.ViewCompat;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroupOverlay;
-import android.view.ViewOverlay;
-import android.widget.FrameLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * <p>
- * Shows a vertical "explode" animation for any view inside a view group (e.g. views inside a
- * ListView). During the animation, a snapshot is taken for the view to the animated and
- * presented in a popup window or view overlay on top of the original view group. The background
- * of the view (a highlight) vertically expands (explodes) during the animation.
- * </p>
- * <p>
- * The exact implementation of the animation depends on platform API level. For JB_MR2 and later,
- * the implementation utilizes ViewOverlay to perform highly performant overlay animations; for
- * older API levels, the implementation falls back to using a full screen popup window to stage
- * the animation.
- * </p>
- * <p>
- * To start this animation, call {@link #startAnimationForView(ViewGroup, View, View, boolean, int)}
- * </p>
- */
-public class ViewGroupItemVerticalExplodeAnimation {
- /**
- * Starts a vertical explode animation for a given view situated in a given container.
- *
- * @param container the container of the view which determines the explode animation's final
- * size
- * @param viewToAnimate the view to be animated. The view will be highlighted by the explode
- * highlight, which expands from the size of the view to the size of the container.
- * @param animationStagingView the view that stages the animation. Since viewToAnimate may be
- * removed from the view tree during the animation, we need a view that'll be alive
- * for the duration of the animation so that the animation won't get cancelled.
- * @param snapshotView whether a snapshot of the view to animate is needed.
- */
- public static void startAnimationForView(final ViewGroup container, final View viewToAnimate,
- final View animationStagingView, final boolean snapshotView, final int duration) {
- if (OsUtil.isAtLeastJB_MR2() && (viewToAnimate.getContext() instanceof Activity)) {
- new ViewExplodeAnimationJellyBeanMR2(viewToAnimate, container, snapshotView, duration)
- .startAnimation();
- } else {
- // Pre JB_MR2, this animation can cause rendering failures which causes the framework
- // to fall back to software rendering where camera preview isn't supported (b/18264647)
- // just skip the animation to avoid this case.
- }
- }
-
- /**
- * Implementation class for API level >= 18.
- */
- @TargetApi(18)
- private static class ViewExplodeAnimationJellyBeanMR2 {
- private final View mViewToAnimate;
- private final ViewGroup mContainer;
- private final View mSnapshot;
- private final Bitmap mViewBitmap;
- private final int mDuration;
-
- public ViewExplodeAnimationJellyBeanMR2(final View viewToAnimate, final ViewGroup container,
- final boolean snapshotView, final int duration) {
- mViewToAnimate = viewToAnimate;
- mContainer = container;
- mDuration = duration;
- if (snapshotView) {
- mViewBitmap = snapshotView(viewToAnimate);
- mSnapshot = new View(viewToAnimate.getContext());
- } else {
- mSnapshot = null;
- mViewBitmap = null;
- }
- }
-
- public void startAnimation() {
- final Context context = mViewToAnimate.getContext();
- final Resources resources = context.getResources();
- final View decorView = ((Activity) context).getWindow().getDecorView();
- final ViewOverlay viewOverlay = decorView.getOverlay();
- if (viewOverlay instanceof ViewGroupOverlay) {
- final ViewGroupOverlay overlay = (ViewGroupOverlay) viewOverlay;
-
- // Add a shadow layer to the overlay.
- final FrameLayout shadowContainerLayer = new FrameLayout(context);
- final Drawable oldBackground = mViewToAnimate.getBackground();
- final Rect containerRect = UiUtils.getMeasuredBoundsOnScreen(mContainer);
- final Rect decorRect = UiUtils.getMeasuredBoundsOnScreen(decorView);
- // Position the container rect relative to the decor rect since the decor rect
- // defines whether the view overlay will be positioned.
- containerRect.offset(-decorRect.left, -decorRect.top);
- shadowContainerLayer.setLeft(containerRect.left);
- shadowContainerLayer.setTop(containerRect.top);
- shadowContainerLayer.setBottom(containerRect.bottom);
- shadowContainerLayer.setRight(containerRect.right);
- shadowContainerLayer.setBackgroundColor(resources.getColor(
- R.color.open_conversation_animation_background_shadow));
- // Per design request, temporarily clear out the background of the item content
- // to not show any ripple effects during animation.
- if (!(oldBackground instanceof ColorDrawable)) {
- mViewToAnimate.setBackground(null);
- }
- overlay.add(shadowContainerLayer);
-
- // Add a expand layer and position it with in the shadow background, so it can
- // be properly clipped to the container bounds during the animation.
- final View expandLayer = new View(context);
- final int elevation = resources.getDimensionPixelSize(
- R.dimen.explode_animation_highlight_elevation);
- final Rect viewRect = UiUtils.getMeasuredBoundsOnScreen(mViewToAnimate);
- // Frame viewRect from screen space to containerRect space.
- viewRect.offset(-containerRect.left - decorRect.left,
- -containerRect.top - decorRect.top);
- // Since the expand layer expands at the same rate above and below, we need to
- // compute the expand scale using the bigger of the top/bottom distances.
- final int expandLayerHalfHeight = viewRect.height() / 2;
- final int topDist = viewRect.top;
- final int bottomDist = containerRect.height() - viewRect.bottom;
- final float scale = expandLayerHalfHeight == 0 ? 1 :
- ((float) Math.max(topDist, bottomDist) + expandLayerHalfHeight) /
- expandLayerHalfHeight;
- // Position the expand layer initially to exactly match the animated item.
- shadowContainerLayer.addView(expandLayer);
- expandLayer.setLeft(viewRect.left);
- expandLayer.setTop(viewRect.top);
- expandLayer.setBottom(viewRect.bottom);
- expandLayer.setRight(viewRect.right);
- expandLayer.setBackgroundColor(resources.getColor(
- R.color.conversation_background));
- ViewCompat.setElevation(expandLayer, elevation);
-
- // Conditionally stage the snapshot in the overlay.
- if (mSnapshot != null) {
- shadowContainerLayer.addView(mSnapshot);
- mSnapshot.setLeft(viewRect.left);
- mSnapshot.setTop(viewRect.top);
- mSnapshot.setBottom(viewRect.bottom);
- mSnapshot.setRight(viewRect.right);
- mSnapshot.setBackground(new BitmapDrawable(resources, mViewBitmap));
- ViewCompat.setElevation(mSnapshot, elevation);
- }
-
- // Apply a scale animation to scale to full screen.
- expandLayer.animate().scaleY(scale)
- .setDuration(mDuration)
- .setInterpolator(UiUtils.EASE_IN_INTERPOLATOR)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- // Clean up the views added to overlay on animation finish.
- overlay.remove(shadowContainerLayer);
- mViewToAnimate.setBackground(oldBackground);
- if (mViewBitmap != null) {
- mViewBitmap.recycle();
- }
- }
- });
- }
- }
- }
-
- /**
- * Take a snapshot of the given review, return a Bitmap object that's owned by the caller.
- */
- static Bitmap snapshotView(final View view) {
- // Save the content of the view into a bitmap.
- final Bitmap viewBitmap = Bitmap.createBitmap(view.getWidth(),
- view.getHeight(), Bitmap.Config.ARGB_8888);
- // Strip the view of its background when taking a snapshot so that things like touch
- // feedback don't get accidentally snapshotted.
- final Drawable viewBackground = view.getBackground();
- ImageUtils.setBackgroundDrawableOnView(view, null);
- view.draw(new Canvas(viewBitmap));
- ImageUtils.setBackgroundDrawableOnView(view, viewBackground);
- return viewBitmap;
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java b/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java
deleted file mode 100644
index b7cb7ae..0000000
--- a/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.appsettings;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.provider.Telephony;
-import android.support.v4.app.NavUtils;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.ApnDatabase;
-import com.android.messaging.sms.BugleApnSettingsLoader;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.PhoneUtils;
-
-public class ApnEditorActivity extends BugleActionBarActivity {
- private static final int ERROR_DIALOG_ID = 0;
- private static final String ERROR_MESSAGE_KEY = "error_msg";
- private ApnEditorFragment mApnEditorFragment;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- // Display the fragment as the main content.
- mApnEditorFragment = new ApnEditorFragment();
- mApnEditorFragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
- ParticipantData.DEFAULT_SELF_SUB_ID));
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, mApnEditorFragment)
- .commit();
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected Dialog onCreateDialog(int id, Bundle args) {
-
- if (id == ERROR_DIALOG_ID) {
- String msg = args.getString(ERROR_MESSAGE_KEY);
-
- return new AlertDialog.Builder(this)
- .setPositiveButton(android.R.string.ok, null)
- .setMessage(msg)
- .create();
- }
-
- return super.onCreateDialog(id);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_BACK: {
- if (mApnEditorFragment.validateAndSave(false)) {
- finish();
- }
- return true;
- }
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
- super.onPrepareDialog(id, dialog);
-
- if (id == ERROR_DIALOG_ID) {
- final String msg = args.getString(ERROR_MESSAGE_KEY);
-
- if (msg != null) {
- ((AlertDialog) dialog).setMessage(msg);
- }
- }
- }
-
- public static class ApnEditorFragment extends PreferenceFragment implements
- SharedPreferences.OnSharedPreferenceChangeListener {
-
- private static final String SAVED_POS = "pos";
-
- private static final int MENU_DELETE = Menu.FIRST;
- private static final int MENU_SAVE = Menu.FIRST + 1;
- private static final int MENU_CANCEL = Menu.FIRST + 2;
-
- private EditTextPreference mMmsProxy;
- private EditTextPreference mMmsPort;
- private EditTextPreference mName;
- private EditTextPreference mMmsc;
- private EditTextPreference mMcc;
- private EditTextPreference mMnc;
- private static String sNotSet;
-
- private String mCurMnc;
- private String mCurMcc;
-
- private Cursor mCursor;
- private boolean mNewApn;
- private boolean mFirstTime;
- private String mCurrentId;
-
- private int mSubId;
-
- /**
- * Standard projection for the interesting columns of a normal note.
- */
- private static final String[] sProjection = new String[] {
- Telephony.Carriers._ID, // 0
- Telephony.Carriers.NAME, // 1
- Telephony.Carriers.MMSC, // 2
- Telephony.Carriers.MCC, // 3
- Telephony.Carriers.MNC, // 4
- Telephony.Carriers.NUMERIC, // 5
- Telephony.Carriers.MMSPROXY, // 6
- Telephony.Carriers.MMSPORT, // 7
- Telephony.Carriers.TYPE, // 8
- };
-
- private static final int ID_INDEX = 0;
- private static final int NAME_INDEX = 1;
- private static final int MMSC_INDEX = 2;
- private static final int MCC_INDEX = 3;
- private static final int MNC_INDEX = 4;
- private static final int NUMERIC_INDEX = 5;
- private static final int MMSPROXY_INDEX = 6;
- private static final int MMSPORT_INDEX = 7;
- private static final int TYPE_INDEX = 8;
-
- private SQLiteDatabase mDatabase;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- addPreferencesFromResource(R.xml.apn_editor);
-
- setHasOptionsMenu(true);
-
- sNotSet = getResources().getString(R.string.apn_not_set);
- mName = (EditTextPreference) findPreference("apn_name");
- mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy");
- mMmsPort = (EditTextPreference) findPreference("apn_mms_port");
- mMmsc = (EditTextPreference) findPreference("apn_mmsc");
- mMcc = (EditTextPreference) findPreference("apn_mcc");
- mMnc = (EditTextPreference) findPreference("apn_mnc");
-
- final Intent intent = getActivity().getIntent();
-
- mFirstTime = savedInstanceState == null;
- mCurrentId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_APN_ROW_ID);
- mNewApn = mCurrentId == null;
-
- mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase();
-
- if (mNewApn) {
- fillUi();
- } else {
- // Do initial query not on the UI thread
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- if (mCurrentId != null) {
- String selection = Telephony.Carriers._ID + " =?";
- String[] selectionArgs = new String[]{ mCurrentId };
- mCursor = mDatabase.query(ApnDatabase.APN_TABLE, sProjection, selection,
- selectionArgs, null, null, null, null);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- if (mCursor == null) {
- getActivity().finish();
- return;
- }
- mCursor.moveToFirst();
-
- fillUi();
- }
- }.execute((Void) null);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onPause() {
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- super.onPause();
- }
-
- public void setSubId(final int subId) {
- mSubId = subId;
- }
-
- private void fillUi() {
- if (mNewApn) {
- mMcc.setText(null);
- mMnc.setText(null);
- String numeric = PhoneUtils.get(mSubId).getSimOperatorNumeric();
- // MCC is first 3 chars and then in 2 - 3 chars of MNC
- if (numeric != null && numeric.length() > 4) {
- // Country code
- String mcc = numeric.substring(0, 3);
- // Network code
- String mnc = numeric.substring(3);
- // Auto populate MNC and MCC for new entries, based on what SIM reports
- mMcc.setText(mcc);
- mMnc.setText(mnc);
- mCurMnc = mnc;
- mCurMcc = mcc;
- }
- mName.setText(null);
- mMmsProxy.setText(null);
- mMmsPort.setText(null);
- mMmsc.setText(null);
- } else if (mFirstTime) {
- mFirstTime = false;
- // Fill in all the values from the db in both text editor and summary
- mName.setText(mCursor.getString(NAME_INDEX));
- mMmsProxy.setText(mCursor.getString(MMSPROXY_INDEX));
- mMmsPort.setText(mCursor.getString(MMSPORT_INDEX));
- mMmsc.setText(mCursor.getString(MMSC_INDEX));
- mMcc.setText(mCursor.getString(MCC_INDEX));
- mMnc.setText(mCursor.getString(MNC_INDEX));
- }
-
- mName.setSummary(checkNull(mName.getText()));
- mMmsProxy.setSummary(checkNull(mMmsProxy.getText()));
- mMmsPort.setSummary(checkNull(mMmsPort.getText()));
- mMmsc.setSummary(checkNull(mMmsc.getText()));
- mMcc.setSummary(checkNull(mMcc.getText()));
- mMnc.setSummary(checkNull(mMnc.getText()));
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- // If it's a new APN, then cancel will delete the new entry in onPause
- if (!mNewApn) {
- menu.add(0, MENU_DELETE, 0, R.string.menu_delete_apn)
- .setIcon(R.drawable.ic_delete_small_dark);
- }
- menu.add(0, MENU_SAVE, 0, R.string.menu_save_apn)
- .setIcon(android.R.drawable.ic_menu_save);
- menu.add(0, MENU_CANCEL, 0, R.string.menu_discard_apn_change)
- .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_DELETE:
- deleteApn();
- return true;
-
- case MENU_SAVE:
- if (validateAndSave(false)) {
- getActivity().finish();
- }
- return true;
-
- case MENU_CANCEL:
- getActivity().finish();
- return true;
-
- case android.R.id.home:
- getActivity().onBackPressed();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onSaveInstanceState(Bundle icicle) {
- super.onSaveInstanceState(icicle);
- if (validateAndSave(true) && mCursor != null) {
- icicle.putInt(SAVED_POS, mCursor.getInt(ID_INDEX));
- }
- }
-
- /**
- * Check the key fields' validity and save if valid.
- * @param force save even if the fields are not valid, if the app is
- * being suspended
- * @return true if the data was saved
- */
- private boolean validateAndSave(boolean force) {
- final String name = checkNotSet(mName.getText());
- final String mcc = checkNotSet(mMcc.getText());
- final String mnc = checkNotSet(mMnc.getText());
-
- if (getErrorMsg() != null && !force) {
- final Bundle bundle = new Bundle();
- bundle.putString(ERROR_MESSAGE_KEY, getErrorMsg());
- getActivity().showDialog(ERROR_DIALOG_ID, bundle);
- return false;
- }
-
- // Make database changes not on the UI thread
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- ContentValues values = new ContentValues();
-
- // Add a dummy name "Untitled", if the user exits the screen without adding a
- // name but entered other information worth keeping.
- values.put(Telephony.Carriers.NAME, name.length() < 1 ?
- getResources().getString(R.string.untitled_apn) : name);
- values.put(Telephony.Carriers.MMSPROXY, checkNotSet(mMmsProxy.getText()));
- values.put(Telephony.Carriers.MMSPORT, checkNotSet(mMmsPort.getText()));
- values.put(Telephony.Carriers.MMSC, checkNotSet(mMmsc.getText()));
-
- values.put(Telephony.Carriers.TYPE, BugleApnSettingsLoader.APN_TYPE_MMS);
-
- values.put(Telephony.Carriers.MCC, mcc);
- values.put(Telephony.Carriers.MNC, mnc);
-
- values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
-
- if (mCurMnc != null && mCurMcc != null) {
- if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) {
- values.put(Telephony.Carriers.CURRENT, 1);
- }
- }
-
- if (mNewApn) {
- mDatabase.insert(ApnDatabase.APN_TABLE, null, values);
- } else {
- // update the APN
- String selection = Telephony.Carriers._ID + " =?";
- String[] selectionArgs = new String[]{ mCurrentId };
- int updated = mDatabase.update(ApnDatabase.APN_TABLE, values,
- selection, selectionArgs);
- }
- return null;
- }
- }.execute((Void) null);
-
- return true;
- }
-
- private void deleteApn() {
- // Make database changes not on the UI thread
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- // delete the APN
- String where = Telephony.Carriers._ID + " =?";
- String[] whereArgs = new String[]{ mCurrentId };
-
- mDatabase.delete(ApnDatabase.APN_TABLE, where, whereArgs);
- return null;
- }
- }.execute((Void) null);
-
- getActivity().finish();
- }
-
- private String checkNull(String value) {
- if (value == null || value.length() == 0) {
- return sNotSet;
- } else {
- return value;
- }
- }
-
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- Preference pref = findPreference(key);
- if (pref != null) {
- pref.setSummary(checkNull(sharedPreferences.getString(key, "")));
- }
- }
-
- private String getErrorMsg() {
- String errorMsg = null;
-
- String name = checkNotSet(mName.getText());
- String mcc = checkNotSet(mMcc.getText());
- String mnc = checkNotSet(mMnc.getText());
-
- if (name.length() < 1) {
- errorMsg = getString(R.string.error_apn_name_empty);
- } else if (mcc.length() != 3) {
- errorMsg = getString(R.string.error_mcc_not3);
- } else if ((mnc.length() & 0xFFFE) != 2) {
- errorMsg = getString(R.string.error_mnc_not23);
- }
-
- return errorMsg;
- }
-
- private String checkNotSet(String value) {
- if (value == null || value.equals(sNotSet)) {
- return "";
- } else {
- return value;
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/ApnPreference.java b/src/com/android/messaging/ui/appsettings/ApnPreference.java
deleted file mode 100644
index 74c6a08..0000000
--- a/src/com/android/messaging/ui/appsettings/ApnPreference.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.appsettings;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.RadioButton;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.ui.UIIntents;
-
-/**
- * ApnPreference implements a pref, typically used as a list item, that has a title/summary on
- * the left and a radio button on the right.
- *
- */
-public class ApnPreference extends Preference implements
- CompoundButton.OnCheckedChangeListener, OnClickListener {
- static final String TAG = "ApnPreference";
-
- public ApnPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public ApnPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.apnPreferenceStyle);
- }
-
- public ApnPreference(Context context) {
- this(context, null);
- }
-
- private static String mSelectedKey = null;
- private static CompoundButton mCurrentChecked = null;
- private boolean mProtectFromCheckedChange = false;
- private boolean mSelectable = true;
- private int mSubId = ParticipantData.DEFAULT_SELF_SUB_ID;
-
- @Override
- public View getView(View convertView, ViewGroup parent) {
- View view = super.getView(convertView, parent);
-
- View widget = view.findViewById(R.id.apn_radiobutton);
- if ((widget != null) && widget instanceof RadioButton) {
- RadioButton rb = (RadioButton) widget;
- if (mSelectable) {
- rb.setOnCheckedChangeListener(this);
-
- boolean isChecked = getKey().equals(mSelectedKey);
- if (isChecked) {
- mCurrentChecked = rb;
- mSelectedKey = getKey();
- }
-
- mProtectFromCheckedChange = true;
- rb.setChecked(isChecked);
- mProtectFromCheckedChange = false;
- } else {
- rb.setVisibility(View.GONE);
- }
- setApnRadioButtonContentDescription(rb);
- }
-
- View textLayout = view.findViewById(R.id.text_layout);
- if ((textLayout != null) && textLayout instanceof RelativeLayout) {
- textLayout.setOnClickListener(this);
- }
-
- return view;
- }
-
- public void setApnRadioButtonContentDescription(final CompoundButton buttonView) {
- final View widget = (View) buttonView.getParent();
- final TextView tv = (TextView) widget.findViewById(android.R.id.title);
- final String apnTitle = tv.getText().toString();
- buttonView.setContentDescription(apnTitle);
- }
-
- public boolean isChecked() {
- return getKey().equals(mSelectedKey);
- }
-
- public void setChecked() {
- mSelectedKey = getKey();
- }
-
- public void setSubId(final int subId) {
- mSubId = subId;
- }
-
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- Log.i(TAG, "ID: " + getKey() + " :" + isChecked);
- if (mProtectFromCheckedChange) {
- return;
- }
-
- if (isChecked) {
- if (mCurrentChecked != null) {
- mCurrentChecked.setChecked(false);
- }
- mCurrentChecked = buttonView;
- mSelectedKey = getKey();
- callChangeListener(mSelectedKey);
- } else {
- mCurrentChecked = null;
- mSelectedKey = null;
- }
- setApnRadioButtonContentDescription(buttonView);
- }
-
- public void onClick(android.view.View v) {
- if ((v != null) && (R.id.text_layout == v.getId())) {
- Context context = getContext();
- if (context != null) {
- context.startActivity(
- UIIntents.get().getApnEditorIntent(context, getKey(), mSubId));
- }
- }
- }
-
- public void setSelectable(boolean selectable) {
- mSelectable = selectable;
- }
-
- public boolean getSelectable() {
- return mSelectable;
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java
deleted file mode 100644
index 28dfc2a..0000000
--- a/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.appsettings;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserManager;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceScreen;
-import android.provider.Telephony;
-import android.support.v4.app.NavUtils;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.ApnDatabase;
-import com.android.messaging.sms.BugleApnSettingsLoader;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-public class ApnSettingsActivity extends BugleActionBarActivity {
- private static final int DIALOG_RESTORE_DEFAULTAPN = 1001;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- // Display the fragment as the main content.
- final ApnSettingsFragment fragment = new ApnSettingsFragment();
- fragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
- ParticipantData.DEFAULT_SELF_SUB_ID));
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, fragment)
- .commit();
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- if (id == DIALOG_RESTORE_DEFAULTAPN) {
- ProgressDialog dialog = new ProgressDialog(this);
- dialog.setMessage(getResources().getString(R.string.restore_default_apn));
- dialog.setCancelable(false);
- return dialog;
- }
- return null;
- }
-
- public static class ApnSettingsFragment extends PreferenceFragment implements
- Preference.OnPreferenceChangeListener {
- public static final String EXTRA_POSITION = "position";
-
- public static final String APN_ID = "apn_id";
-
- private static final String[] APN_PROJECTION = {
- Telephony.Carriers._ID, // 0
- Telephony.Carriers.NAME, // 1
- Telephony.Carriers.APN, // 2
- Telephony.Carriers.TYPE // 3
- };
- private static final int ID_INDEX = 0;
- private static final int NAME_INDEX = 1;
- private static final int APN_INDEX = 2;
- private static final int TYPES_INDEX = 3;
-
- private static final int MENU_NEW = Menu.FIRST;
- private static final int MENU_RESTORE = Menu.FIRST + 1;
-
- private static final int EVENT_RESTORE_DEFAULTAPN_START = 1;
- private static final int EVENT_RESTORE_DEFAULTAPN_COMPLETE = 2;
-
- private static boolean mRestoreDefaultApnMode;
-
- private RestoreApnUiHandler mRestoreApnUiHandler;
- private RestoreApnProcessHandler mRestoreApnProcessHandler;
- private HandlerThread mRestoreDefaultApnThread;
-
- private String mSelectedKey;
-
- private static final ContentValues sCurrentNullMap;
- private static final ContentValues sCurrentSetMap;
-
- private UserManager mUm;
-
- private boolean mUnavailable;
- private int mSubId;
-
- static {
- sCurrentNullMap = new ContentValues(1);
- sCurrentNullMap.putNull(Telephony.Carriers.CURRENT);
-
- sCurrentSetMap = new ContentValues(1);
- sCurrentSetMap.put(Telephony.Carriers.CURRENT, "2"); // 2 for user-selected APN,
- // 1 for Bugle-selected APN
- }
-
- private SQLiteDatabase mDatabase;
-
- public void setSubId(final int subId) {
- mSubId = subId;
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase();
-
- if (OsUtil.isAtLeastL()) {
- mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
- if (!mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
- setHasOptionsMenu(true);
- }
- } else {
- setHasOptionsMenu(true);
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final ListView lv = (ListView) getView().findViewById(android.R.id.list);
- TextView empty = (TextView) getView().findViewById(android.R.id.empty);
- if (empty != null) {
- empty.setText(R.string.apn_settings_not_available);
- lv.setEmptyView(empty);
- }
-
- if (OsUtil.isAtLeastL() &&
- mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
- mUnavailable = true;
- setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
- return;
- }
-
- addPreferencesFromResource(R.xml.apn_settings);
-
- lv.setItemsCanFocus(true);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (mUnavailable) {
- return;
- }
-
- if (!mRestoreDefaultApnMode) {
- fillList();
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- if (mUnavailable) {
- return;
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (mRestoreDefaultApnThread != null) {
- mRestoreDefaultApnThread.quit();
- }
- }
-
- private void fillList() {
- final String mccMnc = PhoneUtils.getMccMncString(PhoneUtils.get(mSubId).getMccMnc());
-
- new AsyncTask<Void, Void, Cursor>() {
- @Override
- protected Cursor doInBackground(Void... params) {
- String selection = Telephony.Carriers.NUMERIC + " =?";
- String[] selectionArgs = new String[]{ mccMnc };
- final Cursor cursor = mDatabase.query(ApnDatabase.APN_TABLE, APN_PROJECTION,
- selection, selectionArgs, null, null, null, null);
- return cursor;
- }
-
- @Override
- protected void onPostExecute(Cursor cursor) {
- if (cursor != null) {
- try {
- PreferenceGroup apnList = (PreferenceGroup)
- findPreference(getString(R.string.apn_list_pref_key));
- apnList.removeAll();
-
- mSelectedKey = BugleApnSettingsLoader.getFirstTryApn(mDatabase, mccMnc);
- while (cursor.moveToNext()) {
- String name = cursor.getString(NAME_INDEX);
- String apn = cursor.getString(APN_INDEX);
- String key = cursor.getString(ID_INDEX);
- String type = cursor.getString(TYPES_INDEX);
-
- if (BugleApnSettingsLoader.isValidApnType(type,
- BugleApnSettingsLoader.APN_TYPE_MMS)) {
- ApnPreference pref = new ApnPreference(getActivity());
- pref.setKey(key);
- pref.setTitle(name);
- pref.setSummary(apn);
- pref.setPersistent(false);
- pref.setOnPreferenceChangeListener(ApnSettingsFragment.this);
- pref.setSelectable(true);
-
- // Turn on the radio button for the currently selected APN. If
- // there is no selected APN, don't select an APN.
- if ((mSelectedKey != null && mSelectedKey.equals(key))) {
- pref.setChecked();
- }
- apnList.addPreference(pref);
- }
- }
- } finally {
- cursor.close();
- }
- }
- }
- }.execute((Void) null);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- if (!mUnavailable) {
- menu.add(0, MENU_NEW, 0,
- getResources().getString(R.string.menu_new_apn))
- .setIcon(R.drawable.ic_add_gray)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(0, MENU_RESTORE, 0,
- getResources().getString(R.string.menu_restore_default_apn))
- .setIcon(android.R.drawable.ic_menu_upload);
- }
-
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_NEW:
- addNewApn();
- return true;
-
- case MENU_RESTORE:
- restoreDefaultApn();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void addNewApn() {
- startActivity(UIIntents.get().getApnEditorIntent(getActivity(), null, mSubId));
- }
-
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
- startActivity(
- UIIntents.get().getApnEditorIntent(getActivity(), preference.getKey(), mSubId));
- return true;
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (newValue instanceof String) {
- setSelectedApnKey((String) newValue);
- }
-
- return true;
- }
-
- // current=2 means user selected APN
- private static final String UPDATE_SELECTION = Telephony.Carriers.CURRENT + " =?";
- private static final String[] UPDATE_SELECTION_ARGS = new String[] { "2" };
- private void setSelectedApnKey(final String key) {
- mSelectedKey = key;
-
- // Make database changes not on the UI thread
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- // null out the previous "current=2" APN
- mDatabase.update(ApnDatabase.APN_TABLE, sCurrentNullMap,
- UPDATE_SELECTION, UPDATE_SELECTION_ARGS);
-
- // set the new "current" APN (2)
- String selection = Telephony.Carriers._ID + " =?";
- String[] selectionArgs = new String[]{ key };
-
- mDatabase.update(ApnDatabase.APN_TABLE, sCurrentSetMap,
- selection, selectionArgs);
- return null;
- }
- }.execute((Void) null);
- }
-
- private boolean restoreDefaultApn() {
- getActivity().showDialog(DIALOG_RESTORE_DEFAULTAPN);
- mRestoreDefaultApnMode = true;
-
- if (mRestoreApnUiHandler == null) {
- mRestoreApnUiHandler = new RestoreApnUiHandler();
- }
-
- if (mRestoreApnProcessHandler == null ||
- mRestoreDefaultApnThread == null) {
- mRestoreDefaultApnThread = new HandlerThread(
- "Restore default APN Handler: Process Thread");
- mRestoreDefaultApnThread.start();
- mRestoreApnProcessHandler = new RestoreApnProcessHandler(
- mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler);
- }
-
- mRestoreApnProcessHandler.sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START);
- return true;
- }
-
- private class RestoreApnUiHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_RESTORE_DEFAULTAPN_COMPLETE:
- fillList();
- getPreferenceScreen().setEnabled(true);
- mRestoreDefaultApnMode = false;
- final Activity activity = getActivity();
- activity.dismissDialog(DIALOG_RESTORE_DEFAULTAPN);
- Toast.makeText(activity, getResources().getString(
- R.string.restore_default_apn_completed), Toast.LENGTH_LONG)
- .show();
- break;
- }
- }
- }
-
- private class RestoreApnProcessHandler extends Handler {
- private Handler mCachedRestoreApnUiHandler;
-
- public RestoreApnProcessHandler(Looper looper, Handler restoreApnUiHandler) {
- super(looper);
- this.mCachedRestoreApnUiHandler = restoreApnUiHandler;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_RESTORE_DEFAULTAPN_START:
- ApnDatabase.forceBuildAndLoadApnTables();
- mCachedRestoreApnUiHandler.sendEmptyMessage(
- EVENT_RESTORE_DEFAULTAPN_COMPLETE);
- break;
- }
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
deleted file mode 100644
index 906009f..0000000
--- a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.appsettings;
-
-import android.app.FragmentTransaction;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.preference.RingtonePreference;
-import android.preference.TwoStatePreference;
-import android.provider.Settings;
-import android.support.v4.app.NavUtils;
-import android.text.TextUtils;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.LicenseActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-public class ApplicationSettingsActivity extends BugleActionBarActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- final boolean topLevel = getIntent().getBooleanExtra(
- UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
- if (topLevel) {
- getSupportActionBar().setTitle(getString(R.string.settings_activity_title));
- }
-
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(android.R.id.content, new ApplicationSettingsFragment());
- ft.commit();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (super.onCreateOptionsMenu(menu)) {
- return true;
- }
- getMenuInflater().inflate(R.menu.settings_menu, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- case R.id.action_license:
- final Intent intent = new Intent(this, LicenseActivity.class);
- startActivity(intent);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- public static class ApplicationSettingsFragment extends PreferenceFragment implements
- OnSharedPreferenceChangeListener {
-
- private String mNotificationsEnabledPreferenceKey;
- private TwoStatePreference mNotificationsEnabledPreference;
- private String mRingtonePreferenceKey;
- private RingtonePreference mRingtonePreference;
- private Preference mVibratePreference;
- private String mSmsDisabledPrefKey;
- private Preference mSmsDisabledPreference;
- private String mSmsEnabledPrefKey;
- private Preference mSmsEnabledPreference;
- private boolean mIsSmsPreferenceClicked;
-
- public ApplicationSettingsFragment() {
- // Required empty constructor
- }
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getPreferenceManager().setSharedPreferencesName(BuglePrefs.SHARED_PREFERENCES_NAME);
- addPreferencesFromResource(R.xml.preferences_application);
-
- mNotificationsEnabledPreferenceKey =
- getString(R.string.notifications_enabled_pref_key);
- mNotificationsEnabledPreference = (TwoStatePreference) findPreference(
- mNotificationsEnabledPreferenceKey);
- mRingtonePreferenceKey = getString(R.string.notification_sound_pref_key);
- mRingtonePreference = (RingtonePreference) findPreference(mRingtonePreferenceKey);
- mVibratePreference = findPreference(
- getString(R.string.notification_vibration_pref_key));
- mSmsDisabledPrefKey = getString(R.string.sms_disabled_pref_key);
- mSmsDisabledPreference = findPreference(mSmsDisabledPrefKey);
- mSmsEnabledPrefKey = getString(R.string.sms_enabled_pref_key);
- mSmsEnabledPreference = findPreference(mSmsEnabledPrefKey);
- mIsSmsPreferenceClicked = false;
-
- final SharedPreferences prefs = getPreferenceScreen().getSharedPreferences();
- updateSoundSummary(prefs);
-
- if (!DebugUtils.isDebugEnabled()) {
- final Preference debugCategory = findPreference(getString(
- R.string.debug_pref_key));
- getPreferenceScreen().removePreference(debugCategory);
- }
-
- final PreferenceScreen advancedScreen = (PreferenceScreen) findPreference(
- getString(R.string.advanced_pref_key));
- final boolean topLevel = getActivity().getIntent().getBooleanExtra(
- UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
- if (topLevel) {
- advancedScreen.setIntent(UIIntents.get()
- .getAdvancedSettingsIntent(getPreferenceScreen().getContext()));
- } else {
- // Hide the Advanced settings screen if this is not top-level; these are shown at
- // the parent SettingsActivity.
- getPreferenceScreen().removePreference(advancedScreen);
- }
- }
-
- @Override
- public boolean onPreferenceTreeClick (PreferenceScreen preferenceScreen,
- Preference preference) {
- if (preference.getKey() == mSmsDisabledPrefKey ||
- preference.getKey() == mSmsEnabledPrefKey) {
- mIsSmsPreferenceClicked = true;
- }
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
-
- private void updateSoundSummary(final SharedPreferences sharedPreferences) {
- // The silent ringtone just returns an empty string
- String ringtoneName = mRingtonePreference.getContext().getString(
- R.string.silent_ringtone);
-
- String ringtoneString = sharedPreferences.getString(mRingtonePreferenceKey, null);
-
- // Bootstrap the default setting in the preferences so that we have a valid selection
- // in the dialog the first time that the user opens it.
- if (ringtoneString == null) {
- ringtoneString = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
- final SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putString(mRingtonePreferenceKey, ringtoneString);
- editor.apply();
- }
-
- if (!TextUtils.isEmpty(ringtoneString)) {
- final Uri ringtoneUri = Uri.parse(ringtoneString);
- final Ringtone tone = RingtoneManager.getRingtone(mRingtonePreference.getContext(),
- ringtoneUri);
-
- if (tone != null) {
- ringtoneName = tone.getTitle(mRingtonePreference.getContext());
- }
- }
-
- mRingtonePreference.setSummary(ringtoneName);
- }
-
- private void updateSmsEnabledPreferences() {
- if (!OsUtil.isAtLeastKLP()) {
- getPreferenceScreen().removePreference(mSmsDisabledPreference);
- getPreferenceScreen().removePreference(mSmsEnabledPreference);
- } else {
- final String defaultSmsAppLabel = getString(R.string.default_sms_app,
- PhoneUtils.getDefault().getDefaultSmsAppLabel());
- boolean isSmsEnabledBeforeState;
- boolean isSmsEnabledCurrentState;
- if (PhoneUtils.getDefault().isDefaultSmsApp()) {
- if (getPreferenceScreen().findPreference(mSmsEnabledPrefKey) == null) {
- getPreferenceScreen().addPreference(mSmsEnabledPreference);
- isSmsEnabledBeforeState = false;
- } else {
- isSmsEnabledBeforeState = true;
- }
- isSmsEnabledCurrentState = true;
- getPreferenceScreen().removePreference(mSmsDisabledPreference);
- mSmsEnabledPreference.setSummary(defaultSmsAppLabel);
- } else {
- if (getPreferenceScreen().findPreference(mSmsDisabledPrefKey) == null) {
- getPreferenceScreen().addPreference(mSmsDisabledPreference);
- isSmsEnabledBeforeState = true;
- } else {
- isSmsEnabledBeforeState = false;
- }
- isSmsEnabledCurrentState = false;
- getPreferenceScreen().removePreference(mSmsEnabledPreference);
- mSmsDisabledPreference.setSummary(defaultSmsAppLabel);
- }
- updateNotificationsPreferences();
- }
- mIsSmsPreferenceClicked = false;
- }
-
- private void updateNotificationsPreferences() {
- final boolean canNotify = !OsUtil.isAtLeastKLP()
- || PhoneUtils.getDefault().isDefaultSmsApp();
- mNotificationsEnabledPreference.setEnabled(canNotify);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- // We do this on start rather than on resume because the sound picker is in a
- // separate activity.
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateSmsEnabledPreferences();
- updateNotificationsPreferences();
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
- final String key) {
- if (key.equals(mNotificationsEnabledPreferenceKey)) {
- updateNotificationsPreferences();
- } else if (key.equals(mRingtonePreferenceKey)) {
- updateSoundSummary(sharedPreferences);
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java b/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java
deleted file mode 100644
index 739d2dc..0000000
--- a/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.appsettings;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.RadioButton;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BuglePrefs;
-
-/**
- * Displays an on/off switch for group MMS setting for a given subscription.
- */
-public class GroupMmsSettingDialog {
- private final Context mContext;
- private final int mSubId;
- private AlertDialog mDialog;
-
- /**
- * Shows a new group MMS setting dialog.
- */
- public static void showDialog(final Context context, final int subId) {
- new GroupMmsSettingDialog(context, subId).show();
- }
-
- private GroupMmsSettingDialog(final Context context, final int subId) {
- mContext = context;
- mSubId = subId;
- }
-
- private void show() {
- Assert.isNull(mDialog);
- mDialog = new AlertDialog.Builder(mContext)
- .setView(createView())
- .setTitle(R.string.group_mms_pref_title)
- .setNegativeButton(android.R.string.cancel, null)
- .show();
- }
-
- private void changeGroupMmsSettings(final boolean enable) {
- Assert.notNull(mDialog);
- BuglePrefs.getSubscriptionPrefs(mSubId).putBoolean(
- mContext.getString(R.string.group_mms_pref_key), enable);
- mDialog.dismiss();
- }
-
- private View createView() {
- final LayoutInflater inflater = (LayoutInflater) mContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- final View rootView = inflater.inflate(R.layout.group_mms_setting_dialog, null, false);
- final RadioButton disableButton = (RadioButton)
- rootView.findViewById(R.id.disable_group_mms_button);
- final RadioButton enableButton = (RadioButton)
- rootView.findViewById(R.id.enable_group_mms_button);
- disableButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- changeGroupMmsSettings(false);
- }
- });
- enableButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- changeGroupMmsSettings(true);
- }
- });
- final boolean mmsEnabled = BuglePrefs.getSubscriptionPrefs(mSubId).getBoolean(
- mContext.getString(R.string.group_mms_pref_key),
- mContext.getResources().getBoolean(R.bool.group_mms_pref_default));
- enableButton.setChecked(mmsEnabled);
- disableButton.setChecked(!mmsEnabled);
- return rootView;
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java b/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java
deleted file mode 100644
index e02823f..0000000
--- a/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.appsettings;
-
-import android.app.FragmentTransaction;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.support.v4.app.NavUtils;
-import android.text.TextUtils;
-import android.view.MenuItem;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.ParticipantRefresh;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.ApnDatabase;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-
-public class PerSubscriptionSettingsActivity extends BugleActionBarActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- final String title = getIntent().getStringExtra(
- UIIntents.UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE);
- if (!TextUtils.isEmpty(title)) {
- getSupportActionBar().setTitle(title);
- } else {
- // This will fall back to the default title, i.e. "Messaging settings," so No-op.
- }
-
- final FragmentTransaction ft = getFragmentManager().beginTransaction();
- final PerSubscriptionSettingsFragment fragment = new PerSubscriptionSettingsFragment();
- ft.replace(android.R.id.content, fragment);
- ft.commit();
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- public static class PerSubscriptionSettingsFragment extends PreferenceFragment
- implements OnSharedPreferenceChangeListener {
- private PhoneNumberPreference mPhoneNumberPreference;
- private Preference mGroupMmsPreference;
- private String mGroupMmsPrefKey;
- private String mPhoneNumberKey;
- private int mSubId;
-
- public PerSubscriptionSettingsFragment() {
- // Required empty constructor
- }
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Get sub id from launch intent
- final Intent intent = getActivity().getIntent();
- Assert.notNull(intent);
- mSubId = (intent != null) ? intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
- ParticipantData.DEFAULT_SELF_SUB_ID) : ParticipantData.DEFAULT_SELF_SUB_ID;
-
- final BuglePrefs subPrefs = Factory.get().getSubscriptionPrefs(mSubId);
- getPreferenceManager().setSharedPreferencesName(subPrefs.getSharedPreferencesName());
- addPreferencesFromResource(R.xml.preferences_per_subscription);
-
- mPhoneNumberKey = getString(R.string.mms_phone_number_pref_key);
- mPhoneNumberPreference = (PhoneNumberPreference) findPreference(mPhoneNumberKey);
- final PreferenceCategory advancedCategory = (PreferenceCategory)
- findPreference(getString(R.string.advanced_category_pref_key));
- final PreferenceCategory mmsCategory = (PreferenceCategory)
- findPreference(getString(R.string.mms_messaging_category_pref_key));
-
- mPhoneNumberPreference.setDefaultPhoneNumber(
- PhoneUtils.get(mSubId).getCanonicalForSelf(false/*allowOverride*/), mSubId);
-
- mGroupMmsPrefKey = getString(R.string.group_mms_pref_key);
- mGroupMmsPreference = findPreference(mGroupMmsPrefKey);
- if (!MmsConfig.get(mSubId).getGroupMmsEnabled()) {
- // Always show group messaging setting even if the SIM has no number
- // If broadcast sms is selected, the SIM number is not needed
- // If group mms is selected, the phone number dialog will popup when message
- // is being sent, making sure we will have a self number for group mms.
- mmsCategory.removePreference(mGroupMmsPreference);
- } else {
- mGroupMmsPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference pref) {
- GroupMmsSettingDialog.showDialog(getActivity(), mSubId);
- return true;
- }
- });
- updateGroupMmsPrefSummary();
- }
-
- if (!MmsConfig.get(mSubId).getSMSDeliveryReportsEnabled()) {
- final Preference deliveryReportsPref = findPreference(
- getString(R.string.delivery_reports_pref_key));
- mmsCategory.removePreference(deliveryReportsPref);
- }
- final Preference wirelessAlertPref = findPreference(getString(
- R.string.wireless_alerts_key));
- if (!isCellBroadcastAppLinkEnabled()) {
- advancedCategory.removePreference(wirelessAlertPref);
- } else {
- wirelessAlertPref.setOnPreferenceClickListener(
- new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- try {
- startActivity(UIIntents.get().getWirelessAlertsIntent());
- } catch (final ActivityNotFoundException e) {
- // Handle so we shouldn't crash if the wireless alerts
- // implementation is broken.
- LogUtil.e(LogUtil.BUGLE_TAG,
- "Failed to launch wireless alerts activity", e);
- }
- return true;
- }
- });
- }
-
- // Access Point Names (APNs)
- final Preference apnsPref = findPreference(getString(R.string.sms_apns_key));
-
- if (MmsUtils.useSystemApnTable() && !ApnDatabase.doesDatabaseExist()) {
- // Don't remove the ability to edit the local APN prefs if this device lets us
- // access the system APN, but we can't find the MCC/MNC in the APN table and we
- // created the local APN table in case the MCC/MNC was in there. In other words,
- // if the local APN table exists, let the user edit it.
- advancedCategory.removePreference(apnsPref);
- } else {
- final PreferenceScreen apnsScreen = (PreferenceScreen) findPreference(
- getString(R.string.sms_apns_key));
- apnsScreen.setIntent(UIIntents.get()
- .getApnSettingsIntent(getPreferenceScreen().getContext(), mSubId));
- }
-
- // We want to disable preferences if we are not the default app, but we do all of the
- // above first so that the user sees the correct information on the screen
- if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
- mGroupMmsPreference.setEnabled(false);
- final Preference autoRetrieveMmsPreference =
- findPreference(getString(R.string.auto_retrieve_mms_pref_key));
- autoRetrieveMmsPreference.setEnabled(false);
- final Preference deliveryReportsPreference =
- findPreference(getString(R.string.delivery_reports_pref_key));
- deliveryReportsPreference.setEnabled(false);
- }
- }
-
- private boolean isCellBroadcastAppLinkEnabled() {
- if (!MmsConfig.get(mSubId).getShowCellBroadcast()) {
- return false;
- }
- try {
- final PackageManager pm = getActivity().getPackageManager();
- return pm.getApplicationEnabledSetting(UIIntents.CMAS_COMPONENT)
- != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
- } catch (final IllegalArgumentException ignored) {
- // CMAS app not installed.
- }
- return false;
- }
-
- private void updateGroupMmsPrefSummary() {
- final boolean groupMmsEnabled = getPreferenceScreen().getSharedPreferences().getBoolean(
- mGroupMmsPrefKey, getResources().getBoolean(R.bool.group_mms_pref_default));
- mGroupMmsPreference.setSummary(groupMmsEnabled ?
- R.string.enable_group_mms : R.string.disable_group_mms);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
- final String key) {
- if (key.equals(mGroupMmsPrefKey)) {
- updateGroupMmsPrefSummary();
- } else if (key.equals(mPhoneNumberKey)) {
- // Save the changed phone number in preferences specific to the sub id
- final String newPhoneNumber = mPhoneNumberPreference.getText();
- final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId);
- if (TextUtils.isEmpty(newPhoneNumber)) {
- subPrefs.remove(mPhoneNumberKey);
- } else {
- subPrefs.putString(getString(R.string.mms_phone_number_pref_key),
- newPhoneNumber);
- }
- // Update the self participants so the new phone number will be reflected
- // everywhere in the UI.
- ParticipantRefresh.refreshSelfParticipants();
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java b/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java
deleted file mode 100644
index 0c9c018..0000000
--- a/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.appsettings;
-
-import android.content.Context;
-import android.preference.EditTextPreference;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Preference that displays a phone number and allows editing via a dialog.
- * <p>
- * A default number can be assigned, which is shown in the preference view and
- * used to populate the dialog editor when the preference value is not set. If
- * the user sets the preference to a number equivalent to the default, the
- * underlying preference is cleared.
- */
-public class PhoneNumberPreference extends EditTextPreference {
-
- private String mDefaultPhoneNumber;
- private int mSubId;
-
- public PhoneNumberPreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mDefaultPhoneNumber = "";
- }
-
- public void setDefaultPhoneNumber(final String phoneNumber, final int subscriptionId) {
- mDefaultPhoneNumber = phoneNumber;
- mSubId = subscriptionId;
- }
-
- @Override
- protected void onBindView(final View view) {
- // Show the preference value if it's set, or the default number if not.
- // If we don't have a default, fall back to a static string (e.g. Unknown).
- String value = getText();
- if (TextUtils.isEmpty(value)) {
- value = mDefaultPhoneNumber;
- }
- final String displayValue = (!TextUtils.isEmpty(value))
- ? PhoneUtils.get(mSubId).formatForDisplay(value)
- : getContext().getString(R.string.unknown_phone_number_pref_display_value);
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String phoneNumber = bidiFormatter.unicodeWrap
- (displayValue, TextDirectionHeuristicsCompat.LTR);
- // Set the value as the summary and let the superclass populate the views
- setSummary(phoneNumber);
- super.onBindView(view);
- }
-
- @Override
- protected void onBindDialogView(final View view) {
- super.onBindDialogView(view);
-
- final String value = getText();
-
- // If the preference is empty, populate the EditText with the default number instead.
- if (TextUtils.isEmpty(value) && !TextUtils.isEmpty(mDefaultPhoneNumber)) {
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String phoneNumber = bidiFormatter.unicodeWrap
- (PhoneUtils.get(mSubId).getCanonicalBySystemLocale(mDefaultPhoneNumber),
- TextDirectionHeuristicsCompat.LTR);
- getEditText().setText(phoneNumber);
- }
- getEditText().setInputType(InputType.TYPE_CLASS_PHONE);
- }
-
- @Override
- protected void onDialogClosed(final boolean positiveResult) {
- if (positiveResult && mDefaultPhoneNumber != null) {
- final String value = getEditText().getText().toString();
- final PhoneUtils phoneUtils = PhoneUtils.get(mSubId);
- final String phoneNumber = phoneUtils.getCanonicalBySystemLocale(value);
- final String defaultPhoneNumber = phoneUtils.getCanonicalBySystemLocale(
- mDefaultPhoneNumber);
-
- // If the new value is the default, clear the preference.
- if (phoneNumber.equals(defaultPhoneNumber)) {
- setText("");
- return;
- }
- }
- super.onDialogClosed(positiveResult);
- }
-
- @Override
- public void setText(final String text) {
- super.setText(text);
-
- // EditTextPreference doesn't show the value on the preference view, but we do.
- // We thus need to force a rebind of the view when a new value is set.
- notifyChanged();
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/SettingsActivity.java b/src/com/android/messaging/ui/appsettings/SettingsActivity.java
deleted file mode 100644
index 75266d8..0000000
--- a/src/com/android/messaging/ui/appsettings/SettingsActivity.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.appsettings;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.NavUtils;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.SettingsData;
-import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
-import com.android.messaging.datamodel.data.SettingsData.SettingsItem;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows the "master" settings activity that contains two parts, one for application-wide settings
- * (dubbed "General settings"), and one or more for per-subscription settings (dubbed "Messaging
- * settings" for single-SIM, and the actual SIM name for multi-SIM). Clicking on either item
- * (e.g. "General settings") will open the detail settings activity (ApplicationSettingsActivity
- * in this case).
- */
-public class SettingsActivity extends BugleActionBarActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- // Directly open the detailed settings page as the top-level settings activity if this is
- // not a multi-SIM device.
- if (PhoneUtils.getDefault().getActiveSubscriptionCount() <= 1) {
- UIIntents.get().launchApplicationSettingsActivity(this, true /* topLevel */);
- finish();
- } else {
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, new SettingsFragment())
- .commit();
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- public static class SettingsFragment extends Fragment implements SettingsDataListener {
- private ListView mListView;
- private SettingsListAdapter mAdapter;
- private final Binding<SettingsData> mBinding = BindingBase.createBinding(this);
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mBinding.bind(DataModel.get().createSettingsData(getActivity(), this));
- mBinding.getData().init(getLoaderManager(), mBinding);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.settings_fragment, container, false);
- mListView = (ListView) view.findViewById(android.R.id.list);
- mAdapter = new SettingsListAdapter(getActivity());
- mListView.setAdapter(mAdapter);
- return view;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- @Override
- public void onSelfParticipantDataLoaded(SettingsData data) {
- mBinding.ensureBound(data);
- mAdapter.setSettingsItems(data.getSettingsItems());
- }
-
- /**
- * An adapter that displays a list of SettingsItem.
- */
- private class SettingsListAdapter extends ArrayAdapter<SettingsItem> {
- public SettingsListAdapter(final Context context) {
- super(context, R.layout.settings_item_view, new ArrayList<SettingsItem>());
- }
-
- public void setSettingsItems(final List<SettingsItem> newList) {
- clear();
- addAll(newList);
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, final View convertView,
- final ViewGroup parent) {
- View itemView;
- if (convertView != null) {
- itemView = convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = inflater.inflate(
- R.layout.settings_item_view, parent, false);
- }
- final SettingsItem item = getItem(position);
- final TextView titleTextView = (TextView) itemView.findViewById(R.id.title);
- final TextView subtitleTextView = (TextView) itemView.findViewById(R.id.subtitle);
- final String summaryText = item.getDisplayDetail();
- titleTextView.setText(item.getDisplayName());
- if (!TextUtils.isEmpty(summaryText)) {
- subtitleTextView.setText(summaryText);
- subtitleTextView.setVisibility(View.VISIBLE);
- } else {
- subtitleTextView.setVisibility(View.GONE);
- }
- itemView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- switch (item.getType()) {
- case SettingsItem.TYPE_GENERAL_SETTINGS:
- UIIntents.get().launchApplicationSettingsActivity(getActivity(),
- false /* topLevel */);
- break;
-
- case SettingsItem.TYPE_PER_SUBSCRIPTION_SETTINGS:
- UIIntents.get().launchPerSubscriptionSettingsActivity(getActivity(),
- item.getSubId(), item.getActivityTitle());
- break;
-
- default:
- Assert.fail("unrecognized setting type!");
- break;
- }
- }
- });
- return itemView;
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java
deleted file mode 100644
index a540597..0000000
--- a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.attachmentchooser;
-
-import android.app.Fragment;
-import android.os.Bundle;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentChooserFragmentHost;
-import com.android.messaging.util.Assert;
-
-public class AttachmentChooserActivity extends BugleActionBarActivity implements
- AttachmentChooserFragmentHost {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.attachment_chooser_activity);
- getSupportActionBar().setDisplayHomeAsUpEnabled(false);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- if (fragment instanceof AttachmentChooserFragment) {
- final String conversationId =
- getIntent().getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- Assert.notNull(conversationId);
- final AttachmentChooserFragment chooserFragment =
- (AttachmentChooserFragment) fragment;
- chooserFragment.setConversationId(conversationId);
- chooserFragment.setHost(this);
- }
- }
-
- @Override
- public void onConfirmSelection() {
- setResult(RESULT_OK);
- finish();
- }
-}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java
deleted file mode 100644
index b39dc3e..0000000
--- a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.attachmentchooser;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.attachmentchooser.AttachmentGridView.AttachmentGridHost;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AttachmentChooserFragment extends Fragment implements DraftMessageDataListener,
- AttachmentGridHost {
- public interface AttachmentChooserFragmentHost {
- void onConfirmSelection();
- }
-
- private AttachmentGridView mAttachmentGridView;
- private AttachmentGridAdapter mAdapter;
- private AttachmentChooserFragmentHost mHost;
-
- @VisibleForTesting
- Binding<DraftMessageData> mBinding = BindingBase.createBinding(this);
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.attachment_chooser_fragment, container, false);
- mAttachmentGridView = (AttachmentGridView) view.findViewById(R.id.grid);
- mAdapter = new AttachmentGridAdapter(getActivity());
- mAttachmentGridView.setAdapter(mAdapter);
- mAttachmentGridView.setHost(this);
- setHasOptionsMenu(true);
- return view;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.attachment_chooser_menu, menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_confirm_selection:
- confirmSelection();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @VisibleForTesting
- void confirmSelection() {
- if (mBinding.isBound()) {
- mBinding.getData().removeExistingAttachments(
- mAttachmentGridView.getUnselectedAttachments());
- mBinding.getData().saveToStorage(mBinding);
- mHost.onConfirmSelection();
- }
- }
-
- public void setConversationId(final String conversationId) {
- mBinding.bind(DataModel.get().createDraftMessageData(conversationId));
- mBinding.getData().addListener(this);
- mBinding.getData().loadFromStorage(mBinding, null, false);
- }
-
- public void setHost(final AttachmentChooserFragmentHost host) {
- mHost = host;
- }
-
- @Override
- public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
- mBinding.ensureBound(data);
- if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
- DraftMessageData.ATTACHMENTS_CHANGED) {
- mAdapter.onAttachmentsLoaded(data.getReadOnlyAttachments());
- }
- }
-
- @Override
- public void onDraftAttachmentLimitReached(final DraftMessageData data) {
- // Do nothing since the user is in the process of unselecting attachments.
- }
-
- @Override
- public void onDraftAttachmentLoadFailed() {
- // Do nothing since the user is in the process of unselecting attachments.
- }
-
- @Override
- public void displayPhoto(final Rect viewRect, final Uri photoUri) {
- final Uri imagesUri = MessagingContentProvider.buildDraftImagesUri(
- mBinding.getData().getConversationId());
- UIIntents.get().launchFullScreenPhotoViewer(
- getActivity(), photoUri, viewRect, imagesUri);
- }
-
- @Override
- public void updateSelectionCount(int count) {
- if (getActivity() instanceof BugleActionBarActivity) {
- final ActionBar actionBar = ((BugleActionBarActivity) getActivity())
- .getSupportActionBar();
- if (actionBar != null) {
- actionBar.setTitle(getResources().getString(
- R.string.attachment_chooser_selection, count));
- }
- }
- }
-
- class AttachmentGridAdapter extends ArrayAdapter<MessagePartData> {
- public AttachmentGridAdapter(final Context context) {
- super(context, R.layout.attachment_grid_item_view, new ArrayList<MessagePartData>());
- }
-
- public void onAttachmentsLoaded(final List<MessagePartData> attachments) {
- clear();
- addAll(attachments);
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- AttachmentGridItemView itemView;
- final MessagePartData item = getItem(position);
- if (convertView != null && convertView instanceof AttachmentGridItemView) {
- itemView = (AttachmentGridItemView) convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = (AttachmentGridItemView) inflater.inflate(
- R.layout.attachment_grid_item_view, parent, false);
- }
- itemView.bind(item, mAttachmentGridView);
- return itemView;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java
deleted file mode 100644
index 8bb7356..0000000
--- a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.attachmentchooser;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.FrameLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.ui.AttachmentPreviewFactory;
-import com.android.messaging.util.Assert;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Shows an item in the attachment picker grid.
- */
-public class AttachmentGridItemView extends FrameLayout {
- public interface HostInterface {
- boolean isItemSelected(MessagePartData attachment);
- void onItemCheckedChanged(AttachmentGridItemView view, MessagePartData attachment);
- void onItemClicked(AttachmentGridItemView view, MessagePartData attachment);
- }
-
- @VisibleForTesting
- MessagePartData mAttachmentData;
- private FrameLayout mAttachmentViewContainer;
- private CheckBox mCheckBox;
- private HostInterface mHostInterface;
-
- public AttachmentGridItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mAttachmentViewContainer = (FrameLayout) findViewById(R.id.attachment_container);
- mCheckBox = (CheckBox) findViewById(R.id.checkbox);
- mCheckBox.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHostInterface.onItemCheckedChanged(AttachmentGridItemView.this, mAttachmentData);
- }
- });
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHostInterface.onItemClicked(AttachmentGridItemView.this, mAttachmentData);
- }
- });
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- // Enlarge the clickable region for the checkbox.
- final int touchAreaIncrease = getResources().getDimensionPixelOffset(
- R.dimen.attachment_grid_checkbox_area_increase);
- final Rect region = new Rect();
- mCheckBox.getHitRect(region);
- region.inset(-touchAreaIncrease, -touchAreaIncrease);
- setTouchDelegate(new TouchDelegate(region, mCheckBox));
- }
- });
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- // The grid view auto-fits the columns, so we want to let the height match the width
- // to make the attachment preview square.
- super.onMeasure(widthMeasureSpec, widthMeasureSpec);
- }
-
- public void bind(final MessagePartData attachment, final HostInterface hostInterface) {
- Assert.isTrue(attachment.isAttachment());
- mHostInterface = hostInterface;
- updateSelectedState();
- if (mAttachmentData == null || !mAttachmentData.equals(attachment)) {
- mAttachmentData = attachment;
- updateAttachmentView();
- }
- }
-
- @VisibleForTesting
- HostInterface testGetHostInterface() {
- return mHostInterface;
- }
-
- public void updateSelectedState() {
- mCheckBox.setChecked(mHostInterface.isItemSelected(mAttachmentData));
- }
-
- private void updateAttachmentView() {
- mAttachmentViewContainer.removeAllViews();
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview(inflater,
- mAttachmentData, mAttachmentViewContainer,
- AttachmentPreviewFactory.TYPE_CHOOSER_GRID, true /* startImageRequest */, null);
- mAttachmentViewContainer.addView(attachmentView);
- }
-}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java
deleted file mode 100644
index abf61dc..0000000
--- a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.attachmentchooser;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.widget.GridView;
-
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentGridAdapter;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Displays a grid of attachment previews for the user to choose which to select/unselect
- */
-public class AttachmentGridView extends GridView implements
- AttachmentGridItemView.HostInterface {
- public interface AttachmentGridHost {
- void displayPhoto(final Rect viewRect, final Uri photoUri);
- void updateSelectionCount(final int count);
- }
-
- // By default everything is selected so only need to keep track of the unselected set.
- private final Set<MessagePartData> mUnselectedSet;
- private AttachmentGridHost mHost;
-
- public AttachmentGridView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mUnselectedSet = new HashSet<>();
- }
-
- public void setHost(final AttachmentGridHost host) {
- mHost = host;
- }
-
- @Override
- public boolean isItemSelected(final MessagePartData attachment) {
- return !mUnselectedSet.contains(attachment);
- }
-
- @Override
- public void onItemClicked(final AttachmentGridItemView view, final MessagePartData attachment) {
- // If the item is an image, show the photo viewer. All the other types (video, audio,
- // vcard) have internal click handling for showing previews so we don't need to handle them
- if (attachment.isImage()) {
- mHost.displayPhoto(UiUtils.getMeasuredBoundsOnScreen(view), attachment.getContentUri());
- }
- }
-
- @Override
- public void onItemCheckedChanged(AttachmentGridItemView view, MessagePartData attachment) {
- // Toggle selection.
- if (isItemSelected(attachment)) {
- mUnselectedSet.add(attachment);
- } else {
- mUnselectedSet.remove(attachment);
- }
- view.updateSelectedState();
- updateSelectionCount();
- }
-
- public Set<MessagePartData> getUnselectedAttachments() {
- return Collections.unmodifiableSet(mUnselectedSet);
- }
-
- private void updateSelectionCount() {
- final int count = getAdapter().getCount() - mUnselectedSet.size();
- Assert.isTrue(count >= 0);
- mHost.updateSelectionCount(count);
- }
-
- private void refreshViews() {
- if (getAdapter() instanceof AttachmentGridAdapter) {
- ((AttachmentGridAdapter) getAdapter()).notifyDataSetChanged();
- }
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- final SavedState savedState = new SavedState(superState);
- savedState.unselectedParts = mUnselectedSet
- .toArray(new MessagePartData[mUnselectedSet.size()]);
- return savedState;
- }
-
- @Override
- public void onRestoreInstanceState(final Parcelable state) {
- if (!(state instanceof SavedState)) {
- super.onRestoreInstanceState(state);
- return;
- }
-
- final SavedState savedState = (SavedState) state;
- super.onRestoreInstanceState(savedState.getSuperState());
- mUnselectedSet.clear();
- for (int i = 0; i < savedState.unselectedParts.length; i++) {
- final MessagePartData unselectedPart = savedState.unselectedParts[i];
- mUnselectedSet.add(unselectedPart);
- }
- refreshViews();
- }
-
- /**
- * Persists the item selection state to saved instance state so we can restore on activity
- * recreation
- */
- public static class SavedState extends BaseSavedState {
- MessagePartData[] unselectedParts;
-
- SavedState(final Parcelable superState) {
- super(superState);
- }
-
- private SavedState(final Parcel in) {
- super(in);
-
- // Read parts
- final int partCount = in.readInt();
- unselectedParts = new MessagePartData[partCount];
- for (int i = 0; i < partCount; i++) {
- unselectedParts[i] = ((MessagePartData) in.readParcelable(
- MessagePartData.class.getClassLoader()));
- }
- }
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- super.writeToParcel(out, flags);
-
- // Write parts
- out.writeInt(unselectedParts.length);
- for (final MessagePartData image : unselectedParts) {
- out.writeParcelable(image, flags);
- }
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(final Parcel in) {
- return new SavedState(in);
- }
- @Override
- public SavedState[] newArray(final int size) {
- return new SavedState[size];
- }
- };
- }
-}
diff --git a/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java b/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java
deleted file mode 100644
index 9c1393d..0000000
--- a/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.AccessibilityUtil;
-
-public class AddContactsConfirmationDialog implements DialogInterface.OnClickListener {
- private final Context mContext;
- private final Uri mAvatarUri;
- private final String mNormalizedDestination;
-
- public AddContactsConfirmationDialog(final Context context, final Uri avatarUri,
- final String normalizedDestination) {
- mContext = context;
- mAvatarUri = avatarUri;
- mNormalizedDestination = normalizedDestination;
- }
-
- public void show() {
- final int confirmAddContactStringId = R.string.add_contact_confirmation;
- final int cancelStringId = android.R.string.cancel;
- final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setTitle(R.string.add_contact_confirmation_dialog_title)
- .setView(createBodyView())
- .setPositiveButton(confirmAddContactStringId, this)
- .setNegativeButton(cancelStringId, null)
- .create();
- alertDialog.show();
- final Resources resources = mContext.getResources();
- final Button cancelButton = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
- if (cancelButton != null) {
- cancelButton.setTextColor(resources.getColor(R.color.contact_picker_button_text_color));
- }
- final Button addButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
- if (addButton != null) {
- addButton.setTextColor(resources.getColor(R.color.contact_picker_button_text_color));
- }
- }
-
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- UIIntents.get().launchAddContactActivity(mContext, mNormalizedDestination);
- }
-
- private View createBodyView() {
- final View view = LayoutInflater.from(mContext).inflate(
- R.layout.add_contacts_confirmation_dialog_body, null);
- final ContactIconView iconView = (ContactIconView) view.findViewById(R.id.contact_icon);
- iconView.setImageResourceUri(mAvatarUri);
- final TextView textView = (TextView) view.findViewById(R.id.participant_name);
- textView.setText(mNormalizedDestination);
- // Accessibility reason : in case phone numbers are mixed in the display name,
- // we need to vocalize it for talkback.
- final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
- mContext.getResources(), mNormalizedDestination);
- textView.setContentDescription(vocalizedDisplayName);
- return view;
- }
-}
diff --git a/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java b/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java
deleted file mode 100644
index 7263c54..0000000
--- a/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.content.Context;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.CustomHeaderPagerListViewHolder;
-import com.android.messaging.ui.contact.ContactListItemView.HostInterface;
-
-/**
- * Holds the all contacts view for the contact picker's view pager.
- */
-public class AllContactsListViewHolder extends CustomHeaderPagerListViewHolder {
- public AllContactsListViewHolder(final Context context, final HostInterface clivHostInterface) {
- super(context, new ContactListAdapter(context, null, clivHostInterface,
- true /* needAlphabetHeader */));
- }
-
- @Override
- protected int getLayoutResId() {
- return R.layout.all_contacts_list_view;
- }
-
- @Override
- protected int getPageTitleResId() {
- return R.string.contact_picker_all_contacts_tab_title;
- }
-
- @Override
- protected int getEmptyViewResId() {
- return R.id.empty_view;
- }
-
- @Override
- protected int getListViewResId() {
- return R.id.all_contacts_list;
- }
-
- @Override
- protected int getEmptyViewTitleResId() {
- return R.string.contact_list_empty_text;
- }
-
- @Override
- protected int getEmptyViewImageResId() {
- return R.drawable.ic_oobe_freq_list;
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java b/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
deleted file mode 100644
index 7df62de..0000000
--- a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.content.Context;
-import android.graphics.drawable.StateListDrawable;
-import android.net.Uri;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import com.android.ex.chips.DropdownChipLayouter;
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ContactListItemData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ContactRecipientEntryUtils;
-
-/**
- * An implementation for {@link DropdownChipLayouter}. Layouts the dropdown
- * list in the ContactRecipientAutoCompleteView in Material style.
- */
-public class ContactDropdownLayouter extends DropdownChipLayouter {
- private final ContactListItemView.HostInterface mClivHostInterface;
-
- public ContactDropdownLayouter(final LayoutInflater inflater, final Context context,
- final ContactListItemView.HostInterface clivHostInterface) {
- super(inflater, context);
- mClivHostInterface = new ContactListItemView.HostInterface() {
-
- @Override
- public void onContactListItemClicked(final ContactListItemData item,
- final ContactListItemView view) {
- // The chips UI will handle auto-complete item click events, so No-op here.
- }
-
- @Override
- public boolean isContactSelected(final ContactListItemData item) {
- // In chips drop down we don't show any selected checkmark per design.
- return false;
- }
- };
- }
-
- /**
- * Bind a drop down view to a RecipientEntry. We'd like regular dropdown items (BASE_RECIPIENT)
- * to behave the same as regular ContactListItemViews, while using the chips library's
- * item styling for alternates dropdown items (happens when you click on a chip).
- */
- @Override
- public View bindView(final View convertView, final ViewGroup parent, final RecipientEntry entry,
- final int position, AdapterType type, final String substring,
- final StateListDrawable deleteDrawable) {
- if (type != AdapterType.BASE_RECIPIENT) {
- if (type == AdapterType.SINGLE_RECIPIENT) {
- // Treat single recipients the same way as alternates. The base implementation of
- // single recipients would try to simplify the destination by tokenizing. We'd
- // like to always show the full destination address per design request.
- type = AdapterType.RECIPIENT_ALTERNATES;
- }
- return super.bindView(convertView, parent, entry, position, type, substring,
- deleteDrawable);
- }
-
- // Default to show all the information
- // RTL : To format contact name and detail if they happen to be phone numbers.
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String displayName = bidiFormatter.unicodeWrap(
- ContactRecipientEntryUtils.getDisplayNameForContactList(entry),
- TextDirectionHeuristicsCompat.LTR);
- final String destination = bidiFormatter.unicodeWrap(
- ContactRecipientEntryUtils.formatDestination(entry),
- TextDirectionHeuristicsCompat.LTR);
- final View itemView = reuseOrInflateView(convertView, parent, type);
-
- // Bold the string that is matched.
- final CharSequence[] styledResults =
- getStyledResults(substring, displayName, destination);
-
- Assert.isTrue(itemView instanceof ContactListItemView);
- final ContactListItemView contactListItemView = (ContactListItemView) itemView;
- contactListItemView.setImageClickHandlerDisabled(true);
- contactListItemView.bind(entry, styledResults[0], styledResults[1],
- mClivHostInterface, (type == AdapterType.SINGLE_RECIPIENT));
- return itemView;
- }
-
- @Override
- protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
- AdapterType type) {
- if (showImage && view instanceof ContactIconView) {
- final ContactIconView contactView = (ContactIconView) view;
- // These show contact cards by default, but that isn't what we want here
- contactView.setImageClickHandlerDisabled(true);
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- ParticipantData.getFromRecipientEntry(entry));
- contactView.setImageResourceUri(avatarUri);
- } else {
- super.bindIconToView(showImage, entry, view, type);
- }
- }
-
- @Override
- protected int getItemLayoutResId(AdapterType type) {
- switch (type) {
- case BASE_RECIPIENT:
- return R.layout.contact_list_item_view;
- case RECIPIENT_ALTERNATES:
- return R.layout.chips_alternates_dropdown_item;
- default:
- return R.layout.chips_alternates_dropdown_item;
- }
- }
-
- @Override
- protected int getAlternateItemLayoutResId(AdapterType type) {
- return getItemLayoutResId(type);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactListAdapter.java b/src/com/android/messaging/ui/contact/ContactListAdapter.java
deleted file mode 100644
index d466b61..0000000
--- a/src/com/android/messaging/ui/contact/ContactListAdapter.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.contact;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CursorAdapter;
-import android.widget.SectionIndexer;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-public class ContactListAdapter extends CursorAdapter implements SectionIndexer {
- private final ContactListItemView.HostInterface mClivHostInterface;
- private final boolean mNeedAlphabetHeader;
- private ContactSectionIndexer mSectionIndexer;
-
- public ContactListAdapter(final Context context, final Cursor cursor,
- final ContactListItemView.HostInterface clivHostInterface,
- final boolean needAlphabetHeader) {
- super(context, cursor, 0);
- mClivHostInterface = clivHostInterface;
- mNeedAlphabetHeader = needAlphabetHeader;
- mSectionIndexer = new ContactSectionIndexer(cursor);
- }
-
- @Override
- public void bindView(final View view, final Context context, final Cursor cursor) {
- Assert.isTrue(view instanceof ContactListItemView);
- final ContactListItemView contactListItemView = (ContactListItemView) view;
- String alphabetHeader = null;
- if (mNeedAlphabetHeader) {
- final int position = cursor.getPosition();
- final int section = mSectionIndexer.getSectionForPosition(position);
- // Check if the position is the first in the section.
- if (mSectionIndexer.getPositionForSection(section) == position) {
- alphabetHeader = (String) mSectionIndexer.getSections()[section];
- }
- }
- contactListItemView.bind(cursor, mClivHostInterface, mNeedAlphabetHeader, alphabetHeader);
- }
-
- @Override
- public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
- final LayoutInflater layoutInflater = LayoutInflater.from(context);
- return layoutInflater.inflate(R.layout.contact_list_item_view, parent, false);
- }
-
- @Override
- public Cursor swapCursor(final Cursor newCursor) {
- mSectionIndexer = new ContactSectionIndexer(newCursor);
- return super.swapCursor(newCursor);
- }
-
- @Override
- public Object[] getSections() {
- return mSectionIndexer.getSections();
- }
-
- @Override
- public int getPositionForSection(final int sectionIndex) {
- return mSectionIndexer.getPositionForSection(sectionIndex);
- }
-
- @Override
- public int getSectionForPosition(final int position) {
- return mSectionIndexer.getSectionForPosition(position);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactListItemView.java b/src/com/android/messaging/ui/contact/ContactListItemView.java
deleted file mode 100644
index 6904da6..0000000
--- a/src/com/android/messaging/ui/contact/ContactListItemView.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.ContactListItemData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * The view for a single entry in a contact list.
- */
-public class ContactListItemView extends LinearLayout implements OnClickListener {
- public interface HostInterface {
- void onContactListItemClicked(ContactListItemData item, ContactListItemView view);
- boolean isContactSelected(ContactListItemData item);
- }
-
- @VisibleForTesting
- final ContactListItemData mData;
- private TextView mContactNameTextView;
- private TextView mContactDetailsTextView;
- private TextView mContactDetailTypeTextView;
- private TextView mAlphabetHeaderTextView;
- private ContactIconView mContactIconView;
- private ImageView mContactCheckmarkView;
- private HostInterface mHostInterface;
- private boolean mShouldShowAlphabetHeader;
-
- public ContactListItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = DataModel.get().createContactListItemData();
- }
-
- @Override
- protected void onFinishInflate () {
- mContactNameTextView = (TextView) findViewById(R.id.contact_name);
- mContactDetailsTextView = (TextView) findViewById(R.id.contact_details);
- mContactDetailTypeTextView = (TextView) findViewById(R.id.contact_detail_type);
- mAlphabetHeaderTextView = (TextView) findViewById(R.id.alphabet_header);
- mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
- mContactCheckmarkView = (ImageView) findViewById(R.id.contact_checkmark);
- }
-
- /**
- * Fills in the data associated with this view by binding to a contact cursor provided by
- * ContactUtil.
- * @param cursor the contact cursor.
- * @param hostInterface host interface to this view.
- * @param shouldShowAlphabetHeader whether an alphabetical header should shown on the side
- * of this view. If {@code headerLabel} is empty, we will still leave space for it.
- * @param headerLabel the alphabetical header on the side of this view, if it should be shown.
- */
- public void bind(final Cursor cursor, final HostInterface hostInterface,
- final boolean shouldShowAlphabetHeader, final String headerLabel) {
- mData.bind(cursor, headerLabel);
- mHostInterface = hostInterface;
- mShouldShowAlphabetHeader = shouldShowAlphabetHeader;
- setOnClickListener(this);
- updateViewAppearance();
- }
-
- /**
- * Binds a RecipientEntry. This is used by the chips text view's dropdown layout.
- * @param recipientEntry the source RecipientEntry provided by ContactDropdownLayouter, which
- * was in turn directly from one of the existing chips, or from filtered results
- * generated by ContactRecipientAdapter.
- * @param styledName display name where the portion that matches the search text is bold.
- * @param styledDestination number where the portion that matches the search text is bold.
- * @param hostInterface host interface to this view.
- * @param isSingleRecipient whether this item is shown as the only line item in the single
- * recipient drop down from the chips view. If this is the case, we always show the
- * contact avatar even if it's not a first-level entry.
- */
- public void bind(final RecipientEntry recipientEntry, final CharSequence styledName,
- final CharSequence styledDestination, final HostInterface hostInterface,
- final boolean isSingleRecipient) {
- mData.bind(recipientEntry, styledName, styledDestination, isSingleRecipient);
- mHostInterface = hostInterface;
- mShouldShowAlphabetHeader = false;
- updateViewAppearance();
- }
-
- private void updateViewAppearance() {
- mContactNameTextView.setText(mData.getDisplayName());
- mContactDetailsTextView.setText(mData.getDestination());
- mContactDetailTypeTextView.setText(Phone.getTypeLabel(getResources(),
- mData.getDestinationType(), mData.getDestinationLabel()));
- final RecipientEntry recipientEntry = mData.getRecipientEntry();
- final String destinationString = String.valueOf(mData.getDestination());
- if (mData.getIsSimpleContactItem()) {
- // This is a special number-with-avatar type of contact (for unknown contact chips
- // and for direct "send to destination" item). In this case, make sure we only show
- // the display name (phone number) and the avatar and hide everything else.
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- ParticipantData.getFromRecipientEntry(recipientEntry));
- mContactIconView.setImageResourceUri(avatarUri, mData.getContactId(),
- mData.getLookupKey(), destinationString);
- mContactIconView.setVisibility(VISIBLE);
- mContactCheckmarkView.setVisibility(GONE);
- mContactDetailTypeTextView.setVisibility(GONE);
- mContactDetailsTextView.setVisibility(GONE);
- mContactNameTextView.setVisibility(VISIBLE);
- } else if (mData.getIsFirstLevel()) {
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- ParticipantData.getFromRecipientEntry(recipientEntry));
- mContactIconView.setImageResourceUri(avatarUri, mData.getContactId(),
- mData.getLookupKey(), destinationString);
- mContactIconView.setVisibility(VISIBLE);
- mContactNameTextView.setVisibility(VISIBLE);
- final boolean isSelected = mHostInterface.isContactSelected(mData);
- setSelected(isSelected);
- mContactCheckmarkView.setVisibility(isSelected ? VISIBLE : GONE);
- mContactDetailsTextView.setVisibility(VISIBLE);
- mContactDetailTypeTextView.setVisibility(VISIBLE);
- } else {
- mContactIconView.setImageResourceUri(null);
- mContactIconView.setVisibility(INVISIBLE);
- mContactNameTextView.setVisibility(GONE);
- final boolean isSelected = mHostInterface.isContactSelected(mData);
- setSelected(isSelected);
- mContactCheckmarkView.setVisibility(isSelected ? VISIBLE : GONE);
- mContactDetailsTextView.setVisibility(VISIBLE);
- mContactDetailTypeTextView.setVisibility(VISIBLE);
- }
-
- if (mShouldShowAlphabetHeader) {
- mAlphabetHeaderTextView.setVisibility(VISIBLE);
- mAlphabetHeaderTextView.setText(mData.getAlphabetHeader());
- } else {
- mAlphabetHeaderTextView.setVisibility(GONE);
- }
- }
-
- /**
- * {@inheritDoc} from OnClickListener
- */
- @Override
- public void onClick(final View v) {
- Assert.isTrue(v == this);
- Assert.isTrue(mHostInterface != null);
- mHostInterface.onContactListItemClicked(mData, this);
- }
-
- public void setImageClickHandlerDisabled(final boolean isHandlerDisabled) {
- mContactIconView.setImageClickHandlerDisabled(isHandlerDisabled);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactPickerFragment.java b/src/com/android/messaging/ui/contact/ContactPickerFragment.java
deleted file mode 100644
index d803087..0000000
--- a/src/com/android/messaging/ui/contact/ContactPickerFragment.java
+++ /dev/null
@@ -1,607 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.contact;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.support.v7.widget.Toolbar;
-import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.transition.Explode;
-import android.transition.Transition;
-import android.transition.Transition.EpicenterCallback;
-import android.transition.TransitionManager;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.action.ActionMonitor;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ContactListItemData;
-import com.android.messaging.datamodel.data.ContactPickerData;
-import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.ui.CustomHeaderPagerViewHolder;
-import com.android.messaging.ui.CustomHeaderViewPager;
-import com.android.messaging.ui.animation.ViewGroupItemVerticalExplodeAnimation;
-import com.android.messaging.ui.contact.ContactRecipientAutoCompleteView.ContactChipsChangeListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Set;
-
-
-/**
- * Shows lists of contacts to start conversations with.
- */
-public class ContactPickerFragment extends Fragment implements ContactPickerDataListener,
- ContactListItemView.HostInterface, ContactChipsChangeListener, OnMenuItemClickListener,
- GetOrCreateConversationActionListener {
- public static final String FRAGMENT_TAG = "contactpicker";
-
- // Undefined contact picker mode. We should never be in this state after the host activity has
- // been created.
- public static final int MODE_UNDEFINED = 0;
-
- // The initial contact picker mode for starting a new conversation with one contact.
- public static final int MODE_PICK_INITIAL_CONTACT = 1;
-
- // The contact picker mode where one initial contact has been picked and we are showing
- // only the chips edit box.
- public static final int MODE_CHIPS_ONLY = 2;
-
- // The contact picker mode for picking more contacts after starting the initial 1-1.
- public static final int MODE_PICK_MORE_CONTACTS = 3;
-
- // The contact picker mode when max number of participants is reached.
- public static final int MODE_PICK_MAX_PARTICIPANTS = 4;
-
- public interface ContactPickerFragmentHost {
- void onGetOrCreateNewConversation(String conversationId);
- void onBackButtonPressed();
- void onInitiateAddMoreParticipants();
- void onParticipantCountChanged(boolean canAddMoreParticipants);
- void invalidateActionBar();
- }
-
- @VisibleForTesting
- final Binding<ContactPickerData> mBinding = BindingBase.createBinding(this);
-
- private ContactPickerFragmentHost mHost;
- private ContactRecipientAutoCompleteView mRecipientTextView;
- private CustomHeaderViewPager mCustomHeaderViewPager;
- private AllContactsListViewHolder mAllContactsListViewHolder;
- private FrequentContactsListViewHolder mFrequentContactsListViewHolder;
- private View mRootView;
- private View mPendingExplodeView;
- private View mComposeDivider;
- private Toolbar mToolbar;
- private int mContactPickingMode = MODE_UNDEFINED;
-
- // Keeps track of the currently selected phone numbers in the chips view to enable fast lookup.
- private Set<String> mSelectedPhoneNumbers = null;
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mAllContactsListViewHolder = new AllContactsListViewHolder(getActivity(), this);
- mFrequentContactsListViewHolder = new FrequentContactsListViewHolder(getActivity(), this);
-
- if (ContactUtil.hasReadContactsPermission()) {
- mBinding.bind(DataModel.get().createContactPickerData(getActivity(), this));
- mBinding.getData().init(getLoaderManager(), mBinding);
- }
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.contact_picker_fragment, container, false);
- mRecipientTextView = (ContactRecipientAutoCompleteView)
- view.findViewById(R.id.recipient_text_view);
- mRecipientTextView.setThreshold(0);
- mRecipientTextView.setDropDownAnchor(R.id.compose_contact_divider);
-
- mRecipientTextView.setContactChipsListener(this);
- mRecipientTextView.setDropdownChipLayouter(new ContactDropdownLayouter(inflater,
- getActivity(), this));
- mRecipientTextView.setAdapter(new ContactRecipientAdapter(getActivity(), this));
- mRecipientTextView.addTextChangedListener(new TextWatcher() {
- @Override
- public void onTextChanged(final CharSequence s, final int start, final int before,
- final int count) {
- }
-
- @Override
- public void beforeTextChanged(final CharSequence s, final int start, final int count,
- final int after) {
- }
-
- @Override
- public void afterTextChanged(final Editable s) {
- updateTextInputButtonsVisibility();
- }
- });
-
- final CustomHeaderPagerViewHolder[] viewHolders = {
- mFrequentContactsListViewHolder,
- mAllContactsListViewHolder };
-
- mCustomHeaderViewPager = (CustomHeaderViewPager) view.findViewById(R.id.contact_pager);
- mCustomHeaderViewPager.setViewHolders(viewHolders);
- mCustomHeaderViewPager.setViewPagerTabHeight(CustomHeaderViewPager.DEFAULT_TAB_STRIP_SIZE);
- mCustomHeaderViewPager.setBackgroundColor(getResources()
- .getColor(R.color.contact_picker_background));
-
- // The view pager defaults to the frequent contacts page.
- mCustomHeaderViewPager.setCurrentItem(0);
-
- mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
- mToolbar.setNavigationIcon(R.drawable.ic_arrow_back_light);
- mToolbar.setNavigationContentDescription(R.string.back);
- mToolbar.setNavigationOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHost.onBackButtonPressed();
- }
- });
-
- mToolbar.inflateMenu(R.menu.compose_menu);
- mToolbar.setOnMenuItemClickListener(this);
-
- mComposeDivider = view.findViewById(R.id.compose_contact_divider);
- mRootView = view;
- return view;
- }
-
- /**
- * {@inheritDoc}
- *
- * Called when the host activity has been created. At this point, the host activity should
- * have set the contact picking mode for us so that we may update our visuals.
- */
- @Override
- public void onActivityCreated(final Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- Assert.isTrue(mContactPickingMode != MODE_UNDEFINED);
- updateVisualsForContactPickingMode(false /* animate */);
- mHost.invalidateActionBar();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- // We could not have bound to the data if the permission was denied.
- if (mBinding.isBound()) {
- mBinding.unbind();
- }
-
- if (mMonitor != null) {
- mMonitor.unregister();
- }
- mMonitor = null;
- }
-
- @Override
- public boolean onMenuItemClick(final MenuItem menuItem) {
- switch (menuItem.getItemId()) {
- case R.id.action_ime_dialpad_toggle:
- final int baseInputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE;
- if ((mRecipientTextView.getInputType() & InputType.TYPE_CLASS_PHONE) !=
- InputType.TYPE_CLASS_PHONE) {
- mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_PHONE);
- menuItem.setIcon(R.drawable.ic_ime_light);
- } else {
- mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_TEXT);
- menuItem.setIcon(R.drawable.ic_numeric_dialpad);
- }
- ImeUtil.get().showImeKeyboard(getActivity(), mRecipientTextView);
- return true;
-
- case R.id.action_add_more_participants:
- mHost.onInitiateAddMoreParticipants();
- return true;
-
- case R.id.action_confirm_participants:
- maybeGetOrCreateConversation();
- return true;
-
- case R.id.action_delete_text:
- Assert.equals(MODE_PICK_INITIAL_CONTACT, mContactPickingMode);
- mRecipientTextView.setText("");
- return true;
- }
- return false;
- }
-
- @Override // From ContactPickerDataListener
- public void onAllContactsCursorUpdated(final Cursor data) {
- mBinding.ensureBound();
- mAllContactsListViewHolder.onContactsCursorUpdated(data);
- }
-
- @Override // From ContactPickerDataListener
- public void onFrequentContactsCursorUpdated(final Cursor data) {
- mBinding.ensureBound();
- mFrequentContactsListViewHolder.onContactsCursorUpdated(data);
- if (data != null && data.getCount() == 0) {
- // Show the all contacts list when there's no frequents.
- mCustomHeaderViewPager.setCurrentItem(1);
- }
- }
-
- @Override // From ContactListItemView.HostInterface
- public void onContactListItemClicked(final ContactListItemData item,
- final ContactListItemView view) {
- if (!isContactSelected(item)) {
- if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
- mPendingExplodeView = view;
- }
- mRecipientTextView.appendRecipientEntry(item.getRecipientEntry());
- } else if (mContactPickingMode != MODE_PICK_INITIAL_CONTACT) {
- mRecipientTextView.removeRecipientEntry(item.getRecipientEntry());
- }
- }
-
- @Override // From ContactListItemView.HostInterface
- public boolean isContactSelected(final ContactListItemData item) {
- return mSelectedPhoneNumbers != null &&
- mSelectedPhoneNumbers.contains(PhoneUtils.getDefault().getCanonicalBySystemLocale(
- item.getRecipientEntry().getDestination()));
- }
-
- /**
- * Call this immediately after attaching the fragment, or when there's a ui state change that
- * changes our host (i.e. restore from saved instance state).
- */
- public void setHost(final ContactPickerFragmentHost host) {
- mHost = host;
- }
-
- public void setContactPickingMode(final int mode, final boolean animate) {
- if (mContactPickingMode != mode) {
- // Guard against impossible transitions.
- Assert.isTrue(
- // We may start from undefined mode to any mode when we are restoring state.
- (mContactPickingMode == MODE_UNDEFINED) ||
- (mContactPickingMode == MODE_PICK_INITIAL_CONTACT && mode == MODE_CHIPS_ONLY) ||
- (mContactPickingMode == MODE_CHIPS_ONLY && mode == MODE_PICK_MORE_CONTACTS) ||
- (mContactPickingMode == MODE_PICK_MORE_CONTACTS
- && mode == MODE_PICK_MAX_PARTICIPANTS) ||
- (mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS
- && mode == MODE_PICK_MORE_CONTACTS));
-
- mContactPickingMode = mode;
- updateVisualsForContactPickingMode(animate);
- }
- }
-
- private void showImeKeyboard() {
- Assert.notNull(mRecipientTextView);
- mRecipientTextView.requestFocus();
-
- // showImeKeyboard() won't work until the layout is ready, so wait until layout is complete
- // before showing the soft keyboard.
- UiUtils.doOnceAfterLayoutChange(mRootView, new Runnable() {
- @Override
- public void run() {
- final Activity activity = getActivity();
- if (activity != null) {
- ImeUtil.get().showImeKeyboard(activity, mRecipientTextView);
- }
- }
- });
- mRecipientTextView.invalidate();
- }
-
- private void updateVisualsForContactPickingMode(final boolean animate) {
- // Don't update visuals if the visuals haven't been inflated yet.
- if (mRootView != null) {
- final Menu menu = mToolbar.getMenu();
- final MenuItem addMoreParticipantsItem = menu.findItem(
- R.id.action_add_more_participants);
- final MenuItem confirmParticipantsItem = menu.findItem(
- R.id.action_confirm_participants);
- switch (mContactPickingMode) {
- case MODE_PICK_INITIAL_CONTACT:
- addMoreParticipantsItem.setVisible(false);
- confirmParticipantsItem.setVisible(false);
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- mComposeDivider.setVisibility(View.INVISIBLE);
- mRecipientTextView.setEnabled(true);
- showImeKeyboard();
- break;
-
- case MODE_CHIPS_ONLY:
- if (animate) {
- if (mPendingExplodeView == null) {
- // The user didn't click on any contact item, so use the toolbar as
- // the view to "explode."
- mPendingExplodeView = mToolbar;
- }
- startExplodeTransitionForContactLists(false /* show */);
-
- ViewGroupItemVerticalExplodeAnimation.startAnimationForView(
- mCustomHeaderViewPager, mPendingExplodeView, mRootView,
- true /* snapshotView */, UiUtils.COMPOSE_TRANSITION_DURATION);
- showHideContactPagerWithAnimation(false /* show */);
- } else {
- mCustomHeaderViewPager.setVisibility(View.GONE);
- }
-
- addMoreParticipantsItem.setVisible(true);
- confirmParticipantsItem.setVisible(false);
- mComposeDivider.setVisibility(View.VISIBLE);
- mRecipientTextView.setEnabled(true);
- break;
-
- case MODE_PICK_MORE_CONTACTS:
- if (animate) {
- // Correctly set the start visibility state for the view pager and
- // individual list items (hidden initially), so that the transition
- // manager can properly track the visibility change for the explode.
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- toggleContactListItemsVisibilityForPendingTransition(false /* show */);
- startExplodeTransitionForContactLists(true /* show */);
- }
- addMoreParticipantsItem.setVisible(false);
- confirmParticipantsItem.setVisible(true);
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- mComposeDivider.setVisibility(View.INVISIBLE);
- mRecipientTextView.setEnabled(true);
- showImeKeyboard();
- break;
-
- case MODE_PICK_MAX_PARTICIPANTS:
- addMoreParticipantsItem.setVisible(false);
- confirmParticipantsItem.setVisible(true);
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- mComposeDivider.setVisibility(View.INVISIBLE);
- // TODO: Verify that this is okay for accessibility
- mRecipientTextView.setEnabled(false);
- break;
-
- default:
- Assert.fail("Unsupported contact picker mode!");
- break;
- }
- updateTextInputButtonsVisibility();
- }
- }
-
- private void updateTextInputButtonsVisibility() {
- final Menu menu = mToolbar.getMenu();
- final MenuItem keypadToggleItem = menu.findItem(R.id.action_ime_dialpad_toggle);
- final MenuItem deleteTextItem = menu.findItem(R.id.action_delete_text);
- if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
- if (TextUtils.isEmpty(mRecipientTextView.getText())) {
- deleteTextItem.setVisible(false);
- keypadToggleItem.setVisible(true);
- } else {
- deleteTextItem.setVisible(true);
- keypadToggleItem.setVisible(false);
- }
- } else {
- deleteTextItem.setVisible(false);
- keypadToggleItem.setVisible(false);
- }
- }
-
- private void maybeGetOrCreateConversation() {
- final ArrayList<ParticipantData> participants =
- mRecipientTextView.getRecipientParticipantDataForConversationCreation();
- if (ContactPickerData.isTooManyParticipants(participants.size())) {
- UiUtils.showToast(R.string.too_many_participants);
- } else if (participants.size() > 0 && mMonitor == null) {
- mMonitor = GetOrCreateConversationAction.getOrCreateConversation(participants,
- null, this);
- }
- }
-
- /**
- * Watches changes in contact chips to determine possible state transitions (e.g. creating
- * the initial conversation, adding more participants or finish the current conversation)
- */
- @Override
- public void onContactChipsChanged(final int oldCount, final int newCount) {
- Assert.isTrue(oldCount != newCount);
- if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
- // Initial picking mode. Start a conversation once a recipient has been picked.
- maybeGetOrCreateConversation();
- } else if (mContactPickingMode == MODE_CHIPS_ONLY) {
- // oldCount == 0 means we are restoring from savedInstanceState to add the existing
- // chips, don't switch to "add more participants" mode in this case.
- if (oldCount > 0 && mRecipientTextView.isFocused()) {
- // Chips only mode. The user may have picked an additional contact or deleted the
- // only existing contact. Either way, switch to picking more participants mode.
- mHost.onInitiateAddMoreParticipants();
- }
- }
- mHost.onParticipantCountChanged(ContactPickerData.getCanAddMoreParticipants(newCount));
-
- // Refresh our local copy of the selected chips set to keep it up-to-date.
- mSelectedPhoneNumbers = mRecipientTextView.getSelectedDestinations();
- invalidateContactLists();
- }
-
- /**
- * Listens for notification that invalid contacts have been removed during resolving them.
- * These contacts were not local contacts, valid email, or valid phone numbers
- */
- @Override
- public void onInvalidContactChipsPruned(final int prunedCount) {
- Assert.isTrue(prunedCount > 0);
- UiUtils.showToast(R.plurals.add_invalid_contact_error, prunedCount);
- }
-
- /**
- * Listens for notification that the user has pressed enter/done on the keyboard with all
- * contacts in place and we should create or go to the existing conversation now
- */
- @Override
- public void onEntryComplete() {
- if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT ||
- mContactPickingMode == MODE_PICK_MORE_CONTACTS ||
- mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS) {
- // Avoid multiple calls to create in race cases (hit done right after selecting contact)
- maybeGetOrCreateConversation();
- }
- }
-
- private void invalidateContactLists() {
- mAllContactsListViewHolder.invalidateList();
- mFrequentContactsListViewHolder.invalidateList();
- }
-
- /**
- * Kicks off a scene transition that animates visibility changes of individual contact list
- * items via explode animation.
- * @param show whether the contact lists are to be shown or hidden.
- */
- private void startExplodeTransitionForContactLists(final boolean show) {
- if (!OsUtil.isAtLeastL()) {
- // Explode animation is not supported pre-L.
- return;
- }
- final Explode transition = new Explode();
- final Rect epicenter = mPendingExplodeView == null ? null :
- UiUtils.getMeasuredBoundsOnScreen(mPendingExplodeView);
- transition.setDuration(UiUtils.COMPOSE_TRANSITION_DURATION);
- transition.setInterpolator(UiUtils.EASE_IN_INTERPOLATOR);
- transition.setEpicenterCallback(new EpicenterCallback() {
- @Override
- public Rect onGetEpicenter(final Transition transition) {
- return epicenter;
- }
- });
-
- // Kick off the delayed scene explode transition. Anything happens after this line in this
- // method before the next frame will be tracked by the transition manager for visibility
- // changes and animated accordingly.
- TransitionManager.beginDelayedTransition(mCustomHeaderViewPager,
- transition);
-
- toggleContactListItemsVisibilityForPendingTransition(show);
- }
-
- /**
- * Toggle the visibility of contact list items in the contact lists for them to be tracked by
- * the transition manager for pending explode transition.
- */
- private void toggleContactListItemsVisibilityForPendingTransition(final boolean show) {
- if (!OsUtil.isAtLeastL()) {
- // Explode animation is not supported pre-L.
- return;
- }
- mAllContactsListViewHolder.toggleVisibilityForPendingTransition(show, mPendingExplodeView);
- mFrequentContactsListViewHolder.toggleVisibilityForPendingTransition(show,
- mPendingExplodeView);
- }
-
- private void showHideContactPagerWithAnimation(final boolean show) {
- final boolean isPagerVisible = (mCustomHeaderViewPager.getVisibility() == View.VISIBLE);
- if (show == isPagerVisible) {
- return;
- }
-
- mCustomHeaderViewPager.animate().alpha(show ? 1F : 0F)
- .setStartDelay(!show ? UiUtils.COMPOSE_TRANSITION_DURATION : 0)
- .withStartAction(new Runnable() {
- @Override
- public void run() {
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- mCustomHeaderViewPager.setAlpha(show ? 0F : 1F);
- }
- })
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mCustomHeaderViewPager.setVisibility(show ? View.VISIBLE : View.GONE);
- mCustomHeaderViewPager.setAlpha(1F);
- }
- });
- }
-
- @Override
- public void onContactCustomColorLoaded(final ContactPickerData data) {
- mBinding.ensureBound(data);
- invalidateContactLists();
- }
-
- public void updateActionBar(final ActionBar actionBar) {
- // Hide the action bar for contact picker mode. The custom ToolBar containing chips UI
- // etc. will take the spot of the action bar.
- actionBar.hide();
- UiUtils.setStatusBarColor(getActivity(),
- getResources().getColor(R.color.compose_notification_bar_background));
- }
-
- private GetOrCreateConversationActionMonitor mMonitor;
-
- @Override
- @RunsOnMainThread
- public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
- final Object data, final String conversationId) {
- Assert.isTrue(monitor == mMonitor);
- Assert.isTrue(conversationId != null);
-
- mRecipientTextView.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE |
- InputType.TYPE_CLASS_TEXT);
- mHost.onGetOrCreateNewConversation(conversationId);
-
- mMonitor = null;
- }
-
- @Override
- @RunsOnMainThread
- public void onGetOrCreateConversationFailed(final ActionMonitor monitor,
- final Object data) {
- Assert.isTrue(monitor == mMonitor);
- LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed");
- mMonitor = null;
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
deleted file mode 100644
index 25f422e..0000000
--- a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MergeCursor;
-import android.support.v4.util.Pair;
-import android.text.TextUtils;
-import android.text.util.Rfc822Token;
-import android.text.util.Rfc822Tokenizer;
-import android.widget.Filter;
-
-import com.android.ex.chips.BaseRecipientAdapter;
-import com.android.ex.chips.RecipientAlternatesAdapter;
-import com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback;
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.ContactRecipientEntryUtils;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * An extension on the base {@link BaseRecipientAdapter} that uses data layer from Bugle,
- * such as the ContactRecipientPhotoManager that uses our own MediaResourceManager, and
- * contact lookup that relies on ContactUtil. It provides data source and filtering ability
- * for {@link ContactRecipientAutoCompleteView}
- */
-public final class ContactRecipientAdapter extends BaseRecipientAdapter {
- public ContactRecipientAdapter(final Context context,
- final ContactListItemView.HostInterface clivHost) {
- this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE, clivHost);
- }
-
- public ContactRecipientAdapter(final Context context, final int preferredMaxResultCount,
- final int queryMode, final ContactListItemView.HostInterface clivHost) {
- super(context, preferredMaxResultCount, queryMode);
- setPhotoManager(new ContactRecipientPhotoManager(context, clivHost));
- }
-
- @Override
- public boolean forceShowAddress() {
- // We should always use the SingleRecipientAddressAdapter
- // And never use the RecipientAlternatesAdapter
- return true;
- }
-
- @Override
- public Filter getFilter() {
- return new ContactFilter();
- }
-
- /**
- * A Filter for a RecipientEditTextView that queries Bugle's ContactUtil for auto-complete
- * results.
- */
- public class ContactFilter extends Filter {
- // Used to sort filtered contacts when it has combined results from email and phone.
- private final RecipientEntryComparator mComparator = new RecipientEntryComparator();
-
- /**
- * Returns a cursor containing the filtered results in contacts given the search text,
- * and a boolean indicating whether the results are sorted.
- *
- * The queries are synchronously performed since this is not run on the main thread.
- *
- * Some locales (e.g. JPN) expect email addresses to be auto-completed for MMS.
- * If this is the case, perform two queries on phone number followed by email and
- * return the merged results.
- */
- @DoesNotRunOnMainThread
- private Pair<Cursor, Boolean> getFilteredResultsCursor(final Context context,
- final String searchText) {
- Assert.isNotMainThread();
- if (BugleGservices.get().getBoolean(
- BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS,
- BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT)) {
- return Pair.create((Cursor) new MergeCursor(new Cursor[] {
- ContactUtil.filterPhones(getContext(), searchText)
- .performSynchronousQuery(),
- ContactUtil.filterEmails(getContext(), searchText)
- .performSynchronousQuery()
- }), false /* the merged cursor is not sorted */);
- } else {
- return Pair.create(ContactUtil.filterDestination(getContext(), searchText)
- .performSynchronousQuery(), true);
- }
- }
-
- @Override
- protected FilterResults performFiltering(final CharSequence constraint) {
- Assert.isNotMainThread();
- final FilterResults results = new FilterResults();
-
- // No query, return empty results.
- if (TextUtils.isEmpty(constraint)) {
- clearTempEntries();
- return results;
- }
-
- final String searchText = constraint.toString();
-
- // Query for auto-complete results, since performFiltering() is not done on the
- // main thread, perform the cursor loader queries directly.
- final Pair<Cursor, Boolean> filteredResults = getFilteredResultsCursor(getContext(),
- searchText);
- final Cursor cursor = filteredResults.first;
- final boolean sorted = filteredResults.second;
- if (cursor != null) {
- try {
- final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
-
- // First check if the constraint is a valid SMS destination. If so, add the
- // destination as a suggestion item to the drop down.
- if (PhoneUtils.isValidSmsMmsDestination(searchText)) {
- entries.add(ContactRecipientEntryUtils
- .constructSendToDestinationEntry(searchText));
- }
-
- HashSet<Long> existingContactIds = new HashSet<Long>();
- while (cursor.moveToNext()) {
- // Make sure there's only one first-level contact (i.e. contact for which
- // we show the avatar picture and name) for every contact id.
- final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- final boolean isFirstLevel = !existingContactIds.contains(contactId);
- if (isFirstLevel) {
- existingContactIds.add(contactId);
- }
- entries.add(ContactUtil.createRecipientEntryForPhoneQuery(cursor,
- isFirstLevel));
- }
-
- if (!sorted) {
- Collections.sort(entries, mComparator);
- }
- results.values = entries;
- results.count = 1;
-
- } finally {
- cursor.close();
- }
- }
- return results;
- }
-
- @Override
- protected void publishResults(final CharSequence constraint, final FilterResults results) {
- mCurrentConstraint = constraint;
- clearTempEntries();
-
- if (results.values != null) {
- @SuppressWarnings("unchecked")
- final List<RecipientEntry> entries = (List<RecipientEntry>) results.values;
- updateEntries(entries);
- } else {
- updateEntries(Collections.<RecipientEntry>emptyList());
- }
- }
-
- private class RecipientEntryComparator implements Comparator<RecipientEntry> {
- private final Collator mCollator;
-
- public RecipientEntryComparator() {
- mCollator = Collator.getInstance(Locale.getDefault());
- mCollator.setStrength(Collator.PRIMARY);
- }
-
- /**
- * Compare two RecipientEntry's, first by locale-aware display name comparison, then by
- * contact id comparison, finally by first-level-ness comparison.
- */
- @Override
- public int compare(RecipientEntry lhs, RecipientEntry rhs) {
- // Send-to-destinations always appear before everything else.
- final boolean sendToLhs = ContactRecipientEntryUtils
- .isSendToDestinationContact(lhs);
- final boolean sendToRhs = ContactRecipientEntryUtils
- .isSendToDestinationContact(lhs);
- if (sendToLhs != sendToRhs) {
- if (sendToLhs) {
- return -1;
- } else if (sendToRhs) {
- return 1;
- }
- }
-
- final int displayNameCompare = mCollator.compare(lhs.getDisplayName(),
- rhs.getDisplayName());
- if (displayNameCompare != 0) {
- return displayNameCompare;
- }
-
- // Long.compare could accomplish the following three lines, but this is only
- // available in API 19+
- final long lhsContactId = lhs.getContactId();
- final long rhsContactId = rhs.getContactId();
- final int contactCompare = lhsContactId < rhsContactId ? -1 :
- (lhsContactId == rhsContactId ? 0 : 1);
- if (contactCompare != 0) {
- return contactCompare;
- }
-
- // These are the same contact. Make sure first-level contacts always
- // appear at the front.
- if (lhs.isFirstLevel()) {
- return -1;
- } else if (rhs.isFirstLevel()) {
- return 1;
- } else {
- return 0;
- }
- }
- }
- }
-
- /**
- * Called when we need to substitute temporary recipient chips with better alternatives.
- * For example, if a list of comma-delimited phone numbers are pasted into the edit box,
- * we want to be able to look up in the ContactUtil for exact matches and get contact
- * details such as name and photo thumbnail for the contact to display a better chip.
- */
- @Override
- public void getMatchingRecipients(final ArrayList<String> inAddresses,
- final RecipientMatchCallback callback) {
- final int addressesSize = Math.min(
- RecipientAlternatesAdapter.MAX_LOOKUPS, inAddresses.size());
- final HashSet<String> addresses = new HashSet<String>();
- for (int i = 0; i < addressesSize; i++) {
- final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
- addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
- }
-
- final Map<String, RecipientEntry> recipientEntries =
- new HashMap<String, RecipientEntry>();
- // query for each address
- for (final String address : addresses) {
- final Cursor cursor = ContactUtil.lookupDestination(getContext(), address)
- .performSynchronousQuery();
- if (cursor != null) {
- try {
- if (cursor.moveToNext()) {
- // There may be multiple matches to the same number, always take the
- // first match.
- // TODO: May need to consider if there's an existing conversation
- // that matches this particular contact and prioritize that contact.
- final RecipientEntry entry =
- ContactUtil.createRecipientEntryForPhoneQuery(cursor, true);
- recipientEntries.put(address, entry);
- }
-
- } finally {
- cursor.close();
- }
- }
- }
-
- // report matches
- callback.matchesFound(recipientEntries);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java b/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java
deleted file mode 100644
index c7c2731..0000000
--- a/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.os.AsyncTask;
-import android.support.v7.appcompat.R;
-import android.text.Editable;
-import android.text.TextPaint;
-import android.text.TextWatcher;
-import android.text.util.Rfc822Tokenizer;
-import android.util.AttributeSet;
-import android.view.ContextThemeWrapper;
-import android.view.KeyEvent;
-import android.view.inputmethod.EditorInfo;
-import android.widget.TextView;
-
-import com.android.ex.chips.RecipientEditTextView;
-import com.android.ex.chips.RecipientEntry;
-import com.android.ex.chips.recipientchip.DrawableRecipientChip;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.util.ContactRecipientEntryUtils;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * An extension for {@link RecipientEditTextView} which shows a list of Materialized contact chips.
- * It uses Bugle's ContactUtil to perform contact lookup, and is able to return the list of
- * recipients in the form of a ParticipantData list.
- */
-public class ContactRecipientAutoCompleteView extends RecipientEditTextView {
- public interface ContactChipsChangeListener {
- void onContactChipsChanged(int oldCount, int newCount);
- void onInvalidContactChipsPruned(int prunedCount);
- void onEntryComplete();
- }
-
- private final int mTextHeight;
- private ContactChipsChangeListener mChipsChangeListener;
-
- /**
- * Watches changes in contact chips to determine possible state transitions.
- */
- private class ContactChipsWatcher implements TextWatcher {
- /**
- * Tracks the old chips count before text changes. Note that we currently don't compare
- * the entire chip sets but just the cheaper-to-do before and after counts, because
- * the chips view don't allow for replacing chips.
- */
- private int mLastChipsCount = 0;
-
- @Override
- public void onTextChanged(final CharSequence s, final int start, final int before,
- final int count) {
- }
-
- @Override
- public void beforeTextChanged(final CharSequence s, final int start, final int count,
- final int after) {
- // We don't take mLastChipsCount from here but from the last afterTextChanged() run.
- // The reason is because at this point, any chip spans to be removed is already removed
- // from s in the chips text view.
- }
-
- @Override
- public void afterTextChanged(final Editable s) {
- final int currentChipsCount = s.getSpans(0, s.length(),
- DrawableRecipientChip.class).length;
- if (currentChipsCount != mLastChipsCount) {
- // When a sanitizing task is running, we don't want to notify any chips count
- // change, but we do want to track the last chip count.
- if (mChipsChangeListener != null && mCurrentSanitizeTask == null) {
- mChipsChangeListener.onContactChipsChanged(mLastChipsCount, currentChipsCount);
- }
- mLastChipsCount = currentChipsCount;
- }
- }
- }
-
- private static final String TEXT_HEIGHT_SAMPLE = "a";
-
- public ContactRecipientAutoCompleteView(final Context context, final AttributeSet attrs) {
- super(new ContextThemeWrapper(context, R.style.ColorAccentGrayOverrideStyle), attrs);
-
- // Get the height of the text, given the currently set font face and size.
- final Rect textBounds = new Rect(0, 0, 0, 0);
- final TextPaint paint = getPaint();
- paint.getTextBounds(TEXT_HEIGHT_SAMPLE, 0, TEXT_HEIGHT_SAMPLE.length(), textBounds);
- mTextHeight = textBounds.height();
-
- setTokenizer(new Rfc822Tokenizer());
- addTextChangedListener(new ContactChipsWatcher());
- setOnFocusListShrinkRecipients(false);
-
- setBackground(context.getResources().getDrawable(
- R.drawable.abc_textfield_search_default_mtrl_alpha));
- }
-
- public void setContactChipsListener(final ContactChipsChangeListener listener) {
- mChipsChangeListener = listener;
- }
-
- /**
- * A tuple of chips which AsyncContactChipSanitizeTask reports as progress to have the
- * chip actually replaced/removed on the UI thread.
- */
- private class ChipReplacementTuple {
- public final DrawableRecipientChip removedChip;
- public final RecipientEntry replacedChipEntry;
-
- public ChipReplacementTuple(final DrawableRecipientChip removedChip,
- final RecipientEntry replacedChipEntry) {
- this.removedChip = removedChip;
- this.replacedChipEntry = replacedChipEntry;
- }
- }
-
- /**
- * An AsyncTask that cleans up contact chips on every chips commit (i.e. get or create a new
- * conversation with the given chips).
- */
- private class AsyncContactChipSanitizeTask extends
- AsyncTask<Void, ChipReplacementTuple, Integer> {
-
- @Override
- protected Integer doInBackground(final Void... params) {
- final DrawableRecipientChip[] recips = getText()
- .getSpans(0, getText().length(), DrawableRecipientChip.class);
- int invalidChipsRemoved = 0;
- for (final DrawableRecipientChip recipient : recips) {
- final RecipientEntry entry = recipient.getEntry();
- if (entry != null) {
- if (entry.isValid()) {
- if (RecipientEntry.isCreatedRecipient(entry.getContactId()) ||
- ContactRecipientEntryUtils.isSendToDestinationContact(entry)) {
- // This is a generated/send-to contact chip, try to look it up and
- // display a chip for the corresponding local contact.
- final Cursor lookupResult = ContactUtil.lookupDestination(getContext(),
- entry.getDestination()).performSynchronousQuery();
- if (lookupResult != null && lookupResult.moveToNext()) {
- // Found a match, remove the generated entry and replace with
- // a better local entry.
- publishProgress(new ChipReplacementTuple(recipient,
- ContactUtil.createRecipientEntryForPhoneQuery(
- lookupResult, true)));
- } else if (PhoneUtils.isValidSmsMmsDestination(
- entry.getDestination())){
- // No match was found, but we have a valid destination so let's at
- // least create an entry that shows an avatar.
- publishProgress(new ChipReplacementTuple(recipient,
- ContactRecipientEntryUtils.constructNumberWithAvatarEntry(
- entry.getDestination())));
- } else {
- // Not a valid contact. Remove and show an error.
- publishProgress(new ChipReplacementTuple(recipient, null));
- invalidChipsRemoved++;
- }
- }
- } else {
- publishProgress(new ChipReplacementTuple(recipient, null));
- invalidChipsRemoved++;
- }
- }
- }
- return invalidChipsRemoved;
- }
-
- @Override
- protected void onProgressUpdate(final ChipReplacementTuple... values) {
- for (final ChipReplacementTuple tuple : values) {
- if (tuple.removedChip != null) {
- final Editable text = getText();
- final int chipStart = text.getSpanStart(tuple.removedChip);
- final int chipEnd = text.getSpanEnd(tuple.removedChip);
- if (chipStart >= 0 && chipEnd >= 0) {
- text.delete(chipStart, chipEnd);
- }
-
- if (tuple.replacedChipEntry != null) {
- appendRecipientEntry(tuple.replacedChipEntry);
- }
- }
- }
- }
-
- @Override
- protected void onPostExecute(final Integer invalidChipsRemoved) {
- mCurrentSanitizeTask = null;
- if (invalidChipsRemoved > 0) {
- mChipsChangeListener.onInvalidContactChipsPruned(invalidChipsRemoved);
- }
- }
- }
-
- /**
- * We don't use SafeAsyncTask but instead use a single threaded executor to ensure that
- * all sanitization tasks are serially executed so as not to interfere with each other.
- */
- private static final Executor SANITIZE_EXECUTOR = Executors.newSingleThreadExecutor();
-
- private AsyncContactChipSanitizeTask mCurrentSanitizeTask;
-
- /**
- * Whenever the caller wants to start a new conversation with the list of chips we have,
- * make sure we asynchronously:
- * 1. Remove invalid chips.
- * 2. Attempt to resolve unknown contacts to known local contacts.
- * 3. Convert still unknown chips to chips with generated avatar.
- *
- * Note that we don't need to perform this synchronously since we can
- * resolve any unknown contacts to local contacts when needed.
- */
- private void sanitizeContactChips() {
- if (mCurrentSanitizeTask != null && !mCurrentSanitizeTask.isCancelled()) {
- mCurrentSanitizeTask.cancel(false);
- mCurrentSanitizeTask = null;
- }
- mCurrentSanitizeTask = new AsyncContactChipSanitizeTask();
- mCurrentSanitizeTask.executeOnExecutor(SANITIZE_EXECUTOR);
- }
-
- /**
- * Returns a list of ParticipantData from the entered chips in order to create
- * new conversation.
- */
- public ArrayList<ParticipantData> getRecipientParticipantDataForConversationCreation() {
- final DrawableRecipientChip[] recips = getText()
- .getSpans(0, getText().length(), DrawableRecipientChip.class);
- final ArrayList<ParticipantData> contacts =
- new ArrayList<ParticipantData>(recips.length);
- for (final DrawableRecipientChip recipient : recips) {
- final RecipientEntry entry = recipient.getEntry();
- if (entry != null && entry.isValid() && entry.getDestination() != null &&
- PhoneUtils.isValidSmsMmsDestination(entry.getDestination())) {
- contacts.add(ParticipantData.getFromRecipientEntry(recipient.getEntry()));
- }
- }
- sanitizeContactChips();
- return contacts;
- }
-
- /**c
- * Gets a set of currently selected chips' emails/phone numbers. This will facilitate the
- * consumer with determining quickly whether a contact is currently selected.
- */
- public Set<String> getSelectedDestinations() {
- Set<String> set = new HashSet<String>();
- final DrawableRecipientChip[] recips = getText()
- .getSpans(0, getText().length(), DrawableRecipientChip.class);
-
- for (final DrawableRecipientChip recipient : recips) {
- final RecipientEntry entry = recipient.getEntry();
- if (entry != null && entry.isValid() && entry.getDestination() != null) {
- set.add(PhoneUtils.getDefault().getCanonicalBySystemLocale(
- entry.getDestination()));
- }
- }
- return set;
- }
-
- @Override
- public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_DONE) {
- mChipsChangeListener.onEntryComplete();
- }
- return super.onEditorAction(view, actionId, event);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java b/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java
deleted file mode 100644
index d69ba64..0000000
--- a/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.content.Context;
-import android.net.Uri;
-
-import com.android.ex.chips.PhotoManager;
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
-import com.android.messaging.datamodel.media.BindableMediaRequest;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.ThreadUtil;
-
-/**
- * An implementation of {@link PhotoManager} that hooks up the chips UI's photos with our own
- * {@link MediaResourceManager} for retrieving and caching contact avatars.
- */
-public class ContactRecipientPhotoManager implements PhotoManager {
- private static final String IMAGE_BYTES_REQUEST_STATIC_BINDING_ID = "imagebytes";
- private final Context mContext;
- private final int mIconSize;
- private final ContactListItemView.HostInterface mClivHostInterface;
-
- public ContactRecipientPhotoManager(final Context context,
- final ContactListItemView.HostInterface clivHostInterface) {
- mContext = context;
- mIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.compose_message_chip_height) - context.getResources().getDimensionPixelSize(
- R.dimen.compose_message_chip_padding) * 2;
- mClivHostInterface = clivHostInterface;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void populatePhotoBytesAsync(final RecipientEntry entry,
- final PhotoManagerCallback callback) {
- // Post all media resource request to the main thread.
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- ParticipantData.getFromRecipientEntry(entry));
- final AvatarRequestDescriptor descriptor =
- new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize);
- final BindableMediaRequest<ImageResource> req = descriptor.buildAsyncMediaRequest(
- mContext,
- new MediaResourceLoadListener<ImageResource>() {
- @Override
- public void onMediaResourceLoaded(final MediaRequest<ImageResource> request,
- final ImageResource resource, final boolean isCached) {
- entry.setPhotoBytes(resource.getBytes());
- callback.onPhotoBytesAsynchronouslyPopulated();
- }
-
- @Override
- public void onMediaResourceLoadError(final MediaRequest<ImageResource> request,
- final Exception exception) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Photo bytes loading failed due to " +
- exception + " request key=" + request.getKey());
-
- // Fall back to the default avatar image.
- callback.onPhotoBytesAsyncLoadFailed();
- }});
-
- // Statically bind the request since it's not bound to any specific piece of UI.
- req.bind(IMAGE_BYTES_REQUEST_STATIC_BINDING_ID);
-
- Factory.get().getMediaResourceManager().requestMediaResourceAsync(req);
- }
- });
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactSectionIndexer.java b/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
deleted file mode 100644
index 1d5abf3..0000000
--- a/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.database.Cursor;
-import android.os.Bundle;
-import android.provider.ContactsContract.Contacts;
-import android.text.TextUtils;
-import android.widget.SectionIndexer;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-
-/**
- * Indexes contact alphabetical sections so we can report to the fast scrolling list view
- * where we are in the list when the user scrolls through the contact list, allowing us to show
- * alphabetical indicators for the fast scroller as well as list section headers.
- */
-public class ContactSectionIndexer implements SectionIndexer {
- private String[] mSections;
- private ArrayList<Integer> mSectionStartingPositions;
- private static final String BLANK_HEADER_STRING = " ";
-
- public ContactSectionIndexer(final Cursor contactsCursor) {
- buildIndexer(contactsCursor);
- }
-
- @Override
- public Object[] getSections() {
- return mSections;
- }
-
- @Override
- public int getPositionForSection(final int sectionIndex) {
- if (mSectionStartingPositions.isEmpty()) {
- return 0;
- }
- // Clamp to the bounds of the section position array per Android API doc.
- return mSectionStartingPositions.get(
- Math.max(Math.min(sectionIndex, mSectionStartingPositions.size() - 1), 0));
- }
-
- @Override
- public int getSectionForPosition(final int position) {
- if (mSectionStartingPositions.isEmpty()) {
- return 0;
- }
-
- // Perform a binary search on the starting positions of the sections to the find the
- // section for the position.
- int left = 0;
- int right = mSectionStartingPositions.size() - 1;
-
- // According to getSectionForPosition()'s doc, we should always clamp the value when the
- // position is out of bound.
- if (position <= mSectionStartingPositions.get(left)) {
- return left;
- } else if (position >= mSectionStartingPositions.get(right)) {
- return right;
- }
-
- while (left <= right) {
- final int mid = (left + right) / 2;
- final int startingPos = mSectionStartingPositions.get(mid);
- final int nextStartingPos = mSectionStartingPositions.get(mid + 1);
- if (position >= startingPos && position < nextStartingPos) {
- return mid;
- } else if (position < startingPos) {
- right = mid - 1;
- } else if (position >= nextStartingPos) {
- left = mid + 1;
- }
- }
- Assert.fail("Invalid section indexer state: couldn't find section for pos " + position);
- return -1;
- }
-
- private boolean buildIndexerFromCursorExtras(final Cursor cursor) {
- if (cursor == null) {
- return false;
- }
- final Bundle cursorExtras = cursor.getExtras();
- if (cursorExtras == null) {
- return false;
- }
- final String[] sections = cursorExtras.getStringArray(
- Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
- final int[] counts = cursorExtras.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
- if (sections == null || counts == null) {
- return false;
- }
-
- if (sections.length != counts.length) {
- return false;
- }
-
- this.mSections = sections;
- mSectionStartingPositions = new ArrayList<Integer>(counts.length);
- int position = 0;
- for (int i = 0; i < counts.length; i++) {
- if (TextUtils.isEmpty(mSections[i])) {
- mSections[i] = BLANK_HEADER_STRING;
- } else if (!mSections[i].equals(BLANK_HEADER_STRING)) {
- mSections[i] = mSections[i].trim();
- }
-
- mSectionStartingPositions.add(position);
- position += counts[i];
- }
- return true;
- }
-
- private void buildIndexerFromDisplayNames(final Cursor cursor) {
- // Loop through the contact cursor and get the starting position for each first character.
- // The result is stored into two arrays, one for the section header (i.e. the first
- // character), and one for the starting position, which is guaranteed to be sorted in
- // ascending order.
- final ArrayList<String> sections = new ArrayList<String>();
- mSectionStartingPositions = new ArrayList<Integer>();
- if (cursor != null) {
- cursor.moveToPosition(-1);
- int currentPosition = 0;
- while (cursor.moveToNext()) {
- // The sort key is typically the contact's display name, so for example, a contact
- // named "Bob" will go into section "B". The Contacts provider generally uses a
- // a slightly more sophisticated heuristic, but as a fallback this is good enough.
- final String sortKey = cursor.getString(ContactUtil.INDEX_SORT_KEY);
- final String section = TextUtils.isEmpty(sortKey) ? BLANK_HEADER_STRING :
- sortKey.substring(0, 1).toUpperCase();
-
- final int lastIndex = sections.size() - 1;
- final String currentSection = lastIndex >= 0 ? sections.get(lastIndex) : null;
- if (!TextUtils.equals(currentSection, section)) {
- sections.add(section);
- mSectionStartingPositions.add(currentPosition);
- }
- currentPosition++;
- }
- }
- mSections = new String[sections.size()];
- sections.toArray(mSections);
- }
-
- private void buildIndexer(final Cursor cursor) {
- // First check if we get indexer label extras from the contact provider; if not, fall back
- // to building from display names.
- if (!buildIndexerFromCursorExtras(cursor)) {
- LogUtil.w(LogUtil.BUGLE_TAG, "contact provider didn't provide contact label " +
- "information, fall back to using display name!");
- buildIndexerFromDisplayNames(cursor);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java b/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java
deleted file mode 100644
index 1f3c795..0000000
--- a/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.contact;
-
-import android.content.Context;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.CustomHeaderPagerListViewHolder;
-import com.android.messaging.ui.contact.ContactListItemView.HostInterface;
-
-/**
- * Holds the frequent contacts view for the contact picker's view pager.
- */
-public class FrequentContactsListViewHolder extends CustomHeaderPagerListViewHolder {
- public FrequentContactsListViewHolder(final Context context,
- final HostInterface clivHostInterface) {
- super(context, new ContactListAdapter(context, null, clivHostInterface,
- false /* needAlphabetHeader */));
- }
-
- @Override
- protected int getLayoutResId() {
- return R.layout.frequent_contacts_list_view;
- }
-
- @Override
- protected int getPageTitleResId() {
- return R.string.contact_picker_frequents_tab_title;
- }
-
- @Override
- protected int getEmptyViewResId() {
- return R.id.empty_view;
- }
-
- @Override
- protected int getListViewResId() {
- return R.id.frequent_contacts_list;
- }
-
- @Override
- protected int getEmptyViewTitleResId() {
- return R.string.contact_list_empty_text;
- }
-
- @Override
- protected int getEmptyViewImageResId() {
- return R.drawable.ic_oobe_freq_list;
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ComposeMessageView.java b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
deleted file mode 100644
index 17f8f74..0000000
--- a/src/com/android/messaging/ui/conversation/ComposeMessageView.java
+++ /dev/null
@@ -1,962 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.text.Editable;
-import android.text.Html;
-import android.text.InputFilter;
-import android.text.InputFilter.LengthFilter;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.view.ContextThemeWrapper;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.EditorInfo;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.ConversationData;
-import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
-import com.android.messaging.datamodel.data.ConversationData.SimpleConversationDataListener;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftForSendTask;
-import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftTaskCallback;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.AttachmentPreview;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.PlainTextEditText;
-import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * This view contains the UI required to generate and send messages.
- */
-public class ComposeMessageView extends LinearLayout
- implements TextView.OnEditorActionListener, DraftMessageDataListener, TextWatcher,
- ConversationInputSink {
-
- public interface IComposeMessageViewHost extends
- DraftMessageData.DraftMessageSubscriptionDataProvider {
- void sendMessage(MessageData message);
- void onComposeEditTextFocused();
- void onAttachmentsCleared();
- void onAttachmentsChanged(final boolean haveAttachments);
- void displayPhoto(Uri photoUri, Rect imageBounds, boolean isDraft);
- void promptForSelfPhoneNumber();
- boolean isReadyForAction();
- void warnOfMissingActionConditions(final boolean sending,
- final Runnable commandToRunAfterActionConditionResolved);
- void warnOfExceedingMessageLimit(final boolean showAttachmentChooser,
- boolean tooManyVideos);
- void notifyOfAttachmentLoadFailed();
- void showAttachmentChooser();
- boolean shouldShowSubjectEditor();
- boolean shouldHideAttachmentsWhenSimSelectorShown();
- Uri getSelfSendButtonIconUri();
- int overrideCounterColor();
- int getAttachmentsClearedFlags();
- }
-
- public static final int CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN = 10;
-
- // There is no draft and there is no need for the SIM selector
- private static final int SEND_WIDGET_MODE_SELF_AVATAR = 1;
- // There is no draft but we need to show the SIM selector
- private static final int SEND_WIDGET_MODE_SIM_SELECTOR = 2;
- // There is a draft
- private static final int SEND_WIDGET_MODE_SEND_BUTTON = 3;
-
- private PlainTextEditText mComposeEditText;
- private PlainTextEditText mComposeSubjectText;
- private TextView mCharCounter;
- private TextView mMmsIndicator;
- private SimIconView mSelfSendIcon;
- private ImageButton mSendButton;
- private View mSubjectView;
- private ImageButton mDeleteSubjectButton;
- private AttachmentPreview mAttachmentPreview;
- private ImageButton mAttachMediaButton;
-
- private final Binding<DraftMessageData> mBinding;
- private IComposeMessageViewHost mHost;
- private final Context mOriginalContext;
- private int mSendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR;
-
- // Shared data model object binding from the conversation.
- private ImmutableBindingRef<ConversationData> mConversationDataModel;
-
- // Centrally manages all the mutual exclusive UI components accepting user input, i.e.
- // media picker, IME keyboard and SIM selector.
- private ConversationInputManager mInputManager;
-
- private final ConversationDataListener mDataListener = new SimpleConversationDataListener() {
- @Override
- public void onConversationMetadataUpdated(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- updateVisualsOnDraftChanged();
- }
-
- @Override
- public void onConversationParticipantDataLoaded(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- updateVisualsOnDraftChanged();
- }
-
- @Override
- public void onSubscriptionListDataLoaded(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- updateOnSelfSubscriptionChange();
- updateVisualsOnDraftChanged();
- }
- };
-
- public ComposeMessageView(final Context context, final AttributeSet attrs) {
- super(new ContextThemeWrapper(context, R.style.ColorAccentBlueOverrideStyle), attrs);
- mOriginalContext = context;
- mBinding = BindingBase.createBinding(this);
- }
-
- /**
- * Host calls this to bind view to DraftMessageData object
- */
- public void bind(final DraftMessageData data, final IComposeMessageViewHost host) {
- mHost = host;
- mBinding.bind(data);
- data.addListener(this);
- data.setSubscriptionDataProvider(host);
-
- final int counterColor = mHost.overrideCounterColor();
- if (counterColor != -1) {
- mCharCounter.setTextColor(counterColor);
- }
- }
-
- /**
- * Host calls this to unbind view
- */
- public void unbind() {
- mBinding.unbind();
- mHost = null;
- mInputManager.onDetach();
- }
-
- @Override
- protected void onFinishInflate() {
- mComposeEditText = (PlainTextEditText) findViewById(
- R.id.compose_message_text);
- mComposeEditText.setOnEditorActionListener(this);
- mComposeEditText.addTextChangedListener(this);
- mComposeEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
- @Override
- public void onFocusChange(final View v, final boolean hasFocus) {
- if (v == mComposeEditText && hasFocus) {
- mHost.onComposeEditTextFocused();
- }
- }
- });
- mComposeEditText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
- hideSimSelector();
- }
- }
- });
-
- // onFinishInflate() is called before self is loaded from db. We set the default text
- // limit here, and apply the real limit later in updateOnSelfSubscriptionChange().
- mComposeEditText.setFilters(new InputFilter[] {
- new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
- .getMaxTextLimit()) });
-
- mSelfSendIcon = (SimIconView) findViewById(R.id.self_send_icon);
- mSelfSendIcon.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- boolean shown = mInputManager.toggleSimSelector(true /* animate */,
- getSelfSubscriptionListEntry());
- hideAttachmentsWhenShowingSims(shown);
- }
- });
- mSelfSendIcon.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View v) {
- if (mHost.shouldShowSubjectEditor()) {
- showSubjectEditor();
- } else {
- boolean shown = mInputManager.toggleSimSelector(true /* animate */,
- getSelfSubscriptionListEntry());
- hideAttachmentsWhenShowingSims(shown);
- }
- return true;
- }
- });
-
- mComposeSubjectText = (PlainTextEditText) findViewById(
- R.id.compose_subject_text);
- // We need the listener to change the avatar to the send button when the user starts
- // typing a subject without a message.
- mComposeSubjectText.addTextChangedListener(this);
- // onFinishInflate() is called before self is loaded from db. We set the default text
- // limit here, and apply the real limit later in updateOnSelfSubscriptionChange().
- mComposeSubjectText.setFilters(new InputFilter[] {
- new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
- .getMaxSubjectLength())});
-
- mDeleteSubjectButton = (ImageButton) findViewById(R.id.delete_subject_button);
- mDeleteSubjectButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View clickView) {
- hideSubjectEditor();
- mComposeSubjectText.setText(null);
- mBinding.getData().setMessageSubject(null);
- }
- });
-
- mSubjectView = findViewById(R.id.subject_view);
-
- mSendButton = (ImageButton) findViewById(R.id.send_message_button);
- mSendButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View clickView) {
- sendMessageInternal(true /* checkMessageSize */);
- }
- });
- mSendButton.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View arg0) {
- boolean shown = mInputManager.toggleSimSelector(true /* animate */,
- getSelfSubscriptionListEntry());
- hideAttachmentsWhenShowingSims(shown);
- if (mHost.shouldShowSubjectEditor()) {
- showSubjectEditor();
- }
- return true;
- }
- });
- mSendButton.setAccessibilityDelegate(new AccessibilityDelegate() {
- @Override
- public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(host, event);
- // When the send button is long clicked, we want TalkBack to announce the real
- // action (select SIM or edit subject), as opposed to "long press send button."
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) {
- event.getText().clear();
- event.getText().add(getResources()
- .getText(shouldShowSimSelector(mConversationDataModel.getData()) ?
- R.string.send_button_long_click_description_with_sim_selector :
- R.string.send_button_long_click_description_no_sim_selector));
- // Make this an announcement so TalkBack will read our custom message.
- event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
- }
- }
- });
-
- mAttachMediaButton =
- (ImageButton) findViewById(R.id.attach_media_button);
- mAttachMediaButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View clickView) {
- // Showing the media picker is treated as starting to compose the message.
- mInputManager.showHideMediaPicker(true /* show */, true /* animate */);
- }
- });
-
- mAttachmentPreview = (AttachmentPreview) findViewById(R.id.attachment_draft_view);
- mAttachmentPreview.setComposeMessageView(this);
-
- mCharCounter = (TextView) findViewById(R.id.char_counter);
- mMmsIndicator = (TextView) findViewById(R.id.mms_indicator);
- }
-
- private void hideAttachmentsWhenShowingSims(final boolean simPickerVisible) {
- if (!mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
- return;
- }
- final boolean haveAttachments = mBinding.getData().hasAttachments();
- if (simPickerVisible && haveAttachments) {
- mHost.onAttachmentsChanged(false);
- mAttachmentPreview.hideAttachmentPreview();
- } else {
- mHost.onAttachmentsChanged(haveAttachments);
- mAttachmentPreview.onAttachmentsChanged(mBinding.getData());
- }
- }
-
- public void setInputManager(final ConversationInputManager inputManager) {
- mInputManager = inputManager;
- }
-
- public void setConversationDataModel(final ImmutableBindingRef<ConversationData> refDataModel) {
- mConversationDataModel = refDataModel;
- mConversationDataModel.getData().addConversationDataListener(mDataListener);
- }
-
- ImmutableBindingRef<DraftMessageData> getDraftDataModel() {
- return BindingBase.createBindingReference(mBinding);
- }
-
- // returns true if it actually shows the subject editor and false if already showing
- private boolean showSubjectEditor() {
- // show the subject editor
- if (mSubjectView.getVisibility() == View.GONE) {
- mSubjectView.setVisibility(View.VISIBLE);
- mSubjectView.requestFocus();
- return true;
- }
- return false;
- }
-
- private void hideSubjectEditor() {
- mSubjectView.setVisibility(View.GONE);
- mComposeEditText.requestFocus();
- }
-
- /**
- * {@inheritDoc} from TextView.OnEditorActionListener
- */
- @Override // TextView.OnEditorActionListener.onEditorAction
- public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_SEND) {
- sendMessageInternal(true /* checkMessageSize */);
- return true;
- }
- return false;
- }
-
- private void sendMessageInternal(final boolean checkMessageSize) {
- LogUtil.i(LogUtil.BUGLE_TAG, "UI initiated message sending in conversation " +
- mBinding.getData().getConversationId());
- if (mBinding.getData().isCheckingDraft()) {
- // Don't send message if we are currently checking draft for sending.
- LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: still checking draft");
- return;
- }
- // Check the host for pre-conditions about any action.
- if (mHost.isReadyForAction()) {
- mInputManager.showHideSimSelector(false /* show */, true /* animate */);
- final String messageToSend = mComposeEditText.getText().toString();
- mBinding.getData().setMessageText(messageToSend);
- final String subject = mComposeSubjectText.getText().toString();
- mBinding.getData().setMessageSubject(subject);
- // Asynchronously check the draft against various requirements before sending.
- mBinding.getData().checkDraftForAction(checkMessageSize,
- mHost.getConversationSelfSubId(), new CheckDraftTaskCallback() {
- @Override
- public void onDraftChecked(DraftMessageData data, int result) {
- mBinding.ensureBound(data);
- switch (result) {
- case CheckDraftForSendTask.RESULT_PASSED:
- // Continue sending after check succeeded.
- final MessageData message = mBinding.getData()
- .prepareMessageForSending(mBinding);
- if (message != null && message.hasContent()) {
- playSentSound();
- mHost.sendMessage(message);
- hideSubjectEditor();
- if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) {
- AccessibilityUtil.announceForAccessibilityCompat(
- ComposeMessageView.this, null,
- R.string.sending_message);
- }
- }
- break;
-
- case CheckDraftForSendTask.RESULT_HAS_PENDING_ATTACHMENTS:
- // Cannot send while there's still attachment(s) being loaded.
- UiUtils.showToastAtBottom(
- R.string.cant_send_message_while_loading_attachments);
- break;
-
- case CheckDraftForSendTask.RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS:
- mHost.promptForSelfPhoneNumber();
- break;
-
- case CheckDraftForSendTask.RESULT_MESSAGE_OVER_LIMIT:
- Assert.isTrue(checkMessageSize);
- mHost.warnOfExceedingMessageLimit(
- true /*sending*/, false /* tooManyVideos */);
- break;
-
- case CheckDraftForSendTask.RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED:
- Assert.isTrue(checkMessageSize);
- mHost.warnOfExceedingMessageLimit(
- true /*sending*/, true /* tooManyVideos */);
- break;
-
- case CheckDraftForSendTask.RESULT_SIM_NOT_READY:
- // Cannot send if there is no active subscription
- UiUtils.showToastAtBottom(
- R.string.cant_send_message_without_active_subscription);
- break;
-
- default:
- break;
- }
- }
- }, mBinding);
- } else {
- mHost.warnOfMissingActionConditions(true /*sending*/,
- new Runnable() {
- @Override
- public void run() {
- sendMessageInternal(checkMessageSize);
- }
-
- });
- }
- }
-
- public static void playSentSound() {
- // Check if this setting is enabled before playing
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- final Context context = Factory.get().getApplicationContext();
- final String prefKey = context.getString(R.string.send_sound_pref_key);
- final boolean defaultValue = context.getResources().getBoolean(
- R.bool.send_sound_pref_default);
- if (!prefs.getBoolean(prefKey, defaultValue)) {
- return;
- }
- MediaUtil.get().playSound(context, R.raw.message_sent, null /* completionListener */);
- }
-
- /**
- * {@inheritDoc} from DraftMessageDataListener
- */
- @Override // From DraftMessageDataListener
- public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
- // As this is called asynchronously when message read check bound before updating text
- mBinding.ensureBound(data);
-
- // We have to cache the values of the DraftMessageData because when we set
- // mComposeEditText, its onTextChanged calls updateVisualsOnDraftChanged,
- // which immediately reloads the text from the subject and message fields and replaces
- // what's in the DraftMessageData.
-
- final String subject = data.getMessageSubject();
- final String message = data.getMessageText();
-
- if ((changeFlags & DraftMessageData.MESSAGE_SUBJECT_CHANGED) ==
- DraftMessageData.MESSAGE_SUBJECT_CHANGED) {
- mComposeSubjectText.setText(subject);
-
- // Set the cursor selection to the end since setText resets it to the start
- mComposeSubjectText.setSelection(mComposeSubjectText.getText().length());
- }
-
- if ((changeFlags & DraftMessageData.MESSAGE_TEXT_CHANGED) ==
- DraftMessageData.MESSAGE_TEXT_CHANGED) {
- mComposeEditText.setText(message);
-
- // Set the cursor selection to the end since setText resets it to the start
- mComposeEditText.setSelection(mComposeEditText.getText().length());
- }
-
- if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
- DraftMessageData.ATTACHMENTS_CHANGED) {
- final boolean haveAttachments = mAttachmentPreview.onAttachmentsChanged(data);
- mHost.onAttachmentsChanged(haveAttachments);
- }
-
- if ((changeFlags & DraftMessageData.SELF_CHANGED) == DraftMessageData.SELF_CHANGED) {
- updateOnSelfSubscriptionChange();
- }
- updateVisualsOnDraftChanged();
- }
-
- @Override // From DraftMessageDataListener
- public void onDraftAttachmentLimitReached(final DraftMessageData data) {
- mBinding.ensureBound(data);
- mHost.warnOfExceedingMessageLimit(false /* sending */, false /* tooManyVideos */);
- }
-
- private void updateOnSelfSubscriptionChange() {
- // Refresh the length filters according to the selected self's MmsConfig.
- mComposeEditText.setFilters(new InputFilter[] {
- new LengthFilter(MmsConfig.get(mBinding.getData().getSelfSubId())
- .getMaxTextLimit()) });
- mComposeSubjectText.setFilters(new InputFilter[] {
- new LengthFilter(MmsConfig.get(mBinding.getData().getSelfSubId())
- .getMaxSubjectLength())});
- }
-
- @Override
- public void onMediaItemsSelected(final Collection<MessagePartData> items) {
- mBinding.getData().addAttachments(items);
- announceMediaItemState(true /*isSelected*/);
- }
-
- @Override
- public void onMediaItemsUnselected(final MessagePartData item) {
- mBinding.getData().removeAttachment(item);
- announceMediaItemState(false /*isSelected*/);
- }
-
- @Override
- public void onPendingAttachmentAdded(final PendingAttachmentData pendingItem) {
- mBinding.getData().addPendingAttachment(pendingItem, mBinding);
- resumeComposeMessage();
- }
-
- private void announceMediaItemState(final boolean isSelected) {
- final Resources res = getContext().getResources();
- final String announcement = isSelected ? res.getString(
- R.string.mediapicker_gallery_item_selected_content_description) :
- res.getString(R.string.mediapicker_gallery_item_unselected_content_description);
- AccessibilityUtil.announceForAccessibilityCompat(
- this, null, announcement);
- }
-
- private void announceAttachmentState() {
- if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) {
- int attachmentCount = mBinding.getData().getReadOnlyAttachments().size()
- + mBinding.getData().getReadOnlyPendingAttachments().size();
- final String announcement = getContext().getResources().getQuantityString(
- R.plurals.attachment_changed_accessibility_announcement,
- attachmentCount, attachmentCount);
- AccessibilityUtil.announceForAccessibilityCompat(
- this, null, announcement);
- }
- }
-
- @Override
- public void resumeComposeMessage() {
- mComposeEditText.requestFocus();
- mInputManager.showHideImeKeyboard(true, true);
- announceAttachmentState();
- }
-
- public void clearAttachments() {
- mBinding.getData().clearAttachments(mHost.getAttachmentsClearedFlags());
- mHost.onAttachmentsCleared();
- }
-
- public void requestDraftMessage(boolean clearLocalDraft) {
- mBinding.getData().loadFromStorage(mBinding, null, clearLocalDraft);
- }
-
- public void setDraftMessage(final MessageData message) {
- mBinding.getData().loadFromStorage(mBinding, message, false);
- }
-
- public void writeDraftMessage() {
- final String messageText = mComposeEditText.getText().toString();
- mBinding.getData().setMessageText(messageText);
-
- final String subject = mComposeSubjectText.getText().toString();
- mBinding.getData().setMessageSubject(subject);
-
- mBinding.getData().saveToStorage(mBinding);
- }
-
- private void updateConversationSelfId(final String selfId, final boolean notify) {
- mBinding.getData().setSelfId(selfId, notify);
- }
-
- private Uri getSelfSendButtonIconUri() {
- final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri();
- if (overridenSelfUri != null) {
- return overridenSelfUri;
- }
- final SubscriptionListEntry subscriptionListEntry = getSelfSubscriptionListEntry();
-
- if (subscriptionListEntry != null) {
- return subscriptionListEntry.selectedIconUri;
- }
-
- // Fall back to default self-avatar in the base case.
- final ParticipantData self = mConversationDataModel.getData().getDefaultSelfParticipant();
- return self == null ? null : AvatarUriUtil.createAvatarUri(self);
- }
-
- private SubscriptionListEntry getSelfSubscriptionListEntry() {
- return mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant(
- mBinding.getData().getSelfId(), false /* excludeDefault */);
- }
-
- private boolean isDataLoadedForMessageSend() {
- // Check data loading prerequisites for sending a message.
- return mConversationDataModel != null && mConversationDataModel.isBound() &&
- mConversationDataModel.getData().getParticipantsLoaded();
- }
-
- private void updateVisualsOnDraftChanged() {
- final String messageText = mComposeEditText.getText().toString();
- final DraftMessageData draftMessageData = mBinding.getData();
- draftMessageData.setMessageText(messageText);
-
- final String subject = mComposeSubjectText.getText().toString();
- draftMessageData.setMessageSubject(subject);
- if (!TextUtils.isEmpty(subject)) {
- mSubjectView.setVisibility(View.VISIBLE);
- }
-
- final boolean hasMessageText = (TextUtils.getTrimmedLength(messageText) > 0);
- final boolean hasSubject = (TextUtils.getTrimmedLength(subject) > 0);
- final boolean hasWorkingDraft = hasMessageText || hasSubject ||
- mBinding.getData().hasAttachments();
-
- // Update the SMS text counter.
- final int messageCount = draftMessageData.getNumMessagesToBeSent();
- final int codePointsRemaining = draftMessageData.getCodePointsRemainingInCurrentMessage();
- // Show the counter only if:
- // - We are not in MMS mode
- // - We are going to send more than one message OR we are getting close
- boolean showCounter = false;
- if (!draftMessageData.getIsMms() && (messageCount > 1 ||
- codePointsRemaining <= CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN)) {
- showCounter = true;
- }
-
- if (showCounter) {
- // Update the remaining characters and number of messages required.
- final String counterText = messageCount > 1 ? codePointsRemaining + " / " +
- messageCount : String.valueOf(codePointsRemaining);
- mCharCounter.setText(counterText);
- mCharCounter.setVisibility(View.VISIBLE);
- } else {
- mCharCounter.setVisibility(View.INVISIBLE);
- }
-
- // Update the send message button. Self icon uri might be null if self participant data
- // and/or conversation metadata hasn't been loaded by the host.
- final Uri selfSendButtonUri = getSelfSendButtonIconUri();
- int sendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR;
- if (selfSendButtonUri != null) {
- if (hasWorkingDraft && isDataLoadedForMessageSend()) {
- UiUtils.revealOrHideViewWithAnimation(mSendButton, VISIBLE, null);
- if (isOverriddenAvatarAGroup()) {
- // If the host has overriden the avatar to show a group avatar where the
- // send button sits, we have to hide the group avatar because it can be larger
- // than the send button and pieces of the avatar will stick out from behind
- // the send button.
- UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, GONE, null);
- }
- mMmsIndicator.setVisibility(draftMessageData.getIsMms() ? VISIBLE : INVISIBLE);
- sendWidgetMode = SEND_WIDGET_MODE_SEND_BUTTON;
- } else {
- mSelfSendIcon.setImageResourceUri(selfSendButtonUri);
- if (isOverriddenAvatarAGroup()) {
- UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, VISIBLE, null);
- }
- UiUtils.revealOrHideViewWithAnimation(mSendButton, GONE, null);
- mMmsIndicator.setVisibility(INVISIBLE);
- if (shouldShowSimSelector(mConversationDataModel.getData())) {
- sendWidgetMode = SEND_WIDGET_MODE_SIM_SELECTOR;
- }
- }
- } else {
- mSelfSendIcon.setImageResourceUri(null);
- }
-
- if (mSendWidgetMode != sendWidgetMode || sendWidgetMode == SEND_WIDGET_MODE_SIM_SELECTOR) {
- setSendButtonAccessibility(sendWidgetMode);
- mSendWidgetMode = sendWidgetMode;
- }
-
- // Update the text hint on the message box depending on the attachment type.
- final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
- final int attachmentCount = attachments.size();
- if (attachmentCount == 0) {
- final SubscriptionListEntry subscriptionListEntry =
- mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant(
- mBinding.getData().getSelfId(), false /* excludeDefault */);
- if (subscriptionListEntry == null) {
- mComposeEditText.setHint(R.string.compose_message_view_hint_text);
- } else {
- mComposeEditText.setHint(Html.fromHtml(getResources().getString(
- R.string.compose_message_view_hint_text_multi_sim,
- subscriptionListEntry.displayName)));
- }
- } else {
- int type = -1;
- for (final MessagePartData attachment : attachments) {
- int newType;
- if (attachment.isImage()) {
- newType = ContentType.TYPE_IMAGE;
- } else if (attachment.isAudio()) {
- newType = ContentType.TYPE_AUDIO;
- } else if (attachment.isVideo()) {
- newType = ContentType.TYPE_VIDEO;
- } else if (attachment.isVCard()) {
- newType = ContentType.TYPE_VCARD;
- } else {
- newType = ContentType.TYPE_OTHER;
- }
-
- if (type == -1) {
- type = newType;
- } else if (type != newType || type == ContentType.TYPE_OTHER) {
- type = ContentType.TYPE_OTHER;
- break;
- }
- }
-
- switch (type) {
- case ContentType.TYPE_IMAGE:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_photo, attachmentCount));
- break;
-
- case ContentType.TYPE_AUDIO:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_audio, attachmentCount));
- break;
-
- case ContentType.TYPE_VIDEO:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_video, attachmentCount));
- break;
-
- case ContentType.TYPE_VCARD:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_vcard, attachmentCount));
- break;
-
- case ContentType.TYPE_OTHER:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_attachments, attachmentCount));
- break;
-
- default:
- Assert.fail("Unsupported attachment type!");
- break;
- }
- }
- }
-
- private void setSendButtonAccessibility(final int sendWidgetMode) {
- switch (sendWidgetMode) {
- case SEND_WIDGET_MODE_SELF_AVATAR:
- // No send button and no SIM selector; the self send button is no longer
- // important for accessibility.
- mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mSelfSendIcon.setContentDescription(null);
- mSendButton.setVisibility(View.GONE);
- setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SELF_AVATAR);
- break;
-
- case SEND_WIDGET_MODE_SIM_SELECTOR:
- mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- mSelfSendIcon.setContentDescription(getSimContentDescription());
- setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SIM_SELECTOR);
- break;
-
- case SEND_WIDGET_MODE_SEND_BUTTON:
- mMmsIndicator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mMmsIndicator.setContentDescription(null);
- setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SEND_BUTTON);
- break;
- }
- }
-
- private String getSimContentDescription() {
- final SubscriptionListEntry sub = getSelfSubscriptionListEntry();
- if (sub != null) {
- return getResources().getString(
- R.string.sim_selector_button_content_description_with_selection,
- sub.displayName);
- } else {
- return getResources().getString(
- R.string.sim_selector_button_content_description);
- }
- }
-
- // Set accessibility traversal order of the components in the send widget.
- private void setSendWidgetAccessibilityTraversalOrder(final int mode) {
- if (OsUtil.isAtLeastL_MR1()) {
- mAttachMediaButton.setAccessibilityTraversalBefore(R.id.compose_message_text);
- switch (mode) {
- case SEND_WIDGET_MODE_SIM_SELECTOR:
- mComposeEditText.setAccessibilityTraversalBefore(R.id.self_send_icon);
- break;
- case SEND_WIDGET_MODE_SEND_BUTTON:
- mComposeEditText.setAccessibilityTraversalBefore(R.id.send_message_button);
- break;
- default:
- break;
- }
- }
- }
-
- @Override
- public void afterTextChanged(final Editable editable) {
- }
-
- @Override
- public void beforeTextChanged(final CharSequence s, final int start, final int count,
- final int after) {
- if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
- hideSimSelector();
- }
- }
-
- private void hideSimSelector() {
- if (mInputManager.showHideSimSelector(false /* show */, true /* animate */)) {
- // Now that the sim selector has been hidden, reshow the attachments if they
- // have been hidden.
- hideAttachmentsWhenShowingSims(false /*simPickerVisible*/);
- }
- }
-
- @Override
- public void onTextChanged(final CharSequence s, final int start, final int before,
- final int count) {
- final BugleActionBarActivity activity = (mOriginalContext instanceof BugleActionBarActivity)
- ? (BugleActionBarActivity) mOriginalContext : null;
- if (activity != null && activity.getIsDestroyed()) {
- LogUtil.v(LogUtil.BUGLE_TAG, "got onTextChanged after onDestroy");
-
- // if we get onTextChanged after the activity is destroyed then, ah, wtf
- // b/18176615
- // This appears to have occurred as the result of orientation change.
- return;
- }
-
- mBinding.ensureBound();
- updateVisualsOnDraftChanged();
- }
-
- @Override
- public PlainTextEditText getComposeEditText() {
- return mComposeEditText;
- }
-
- public void displayPhoto(final Uri photoUri, final Rect imageBounds) {
- mHost.displayPhoto(photoUri, imageBounds, true /* isDraft */);
- }
-
- public void updateConversationSelfIdOnExternalChange(final String selfId) {
- updateConversationSelfId(selfId, true /* notify */);
- }
-
- /**
- * The selfId of the conversation. As soon as the DraftMessageData successfully loads (i.e.
- * getSelfId() is non-null), the selfId in DraftMessageData is treated as the sole source
- * of truth for conversation self id since it reflects any pending self id change the user
- * makes in the UI.
- */
- public String getConversationSelfId() {
- return mBinding.getData().getSelfId();
- }
-
- public void selectSim(SubscriptionListEntry subscriptionData) {
- final String oldSelfId = getConversationSelfId();
- final String newSelfId = subscriptionData.selfParticipantId;
- Assert.notNull(newSelfId);
- // Don't attempt to change self if self hasn't been loaded, or if self hasn't changed.
- if (oldSelfId == null || TextUtils.equals(oldSelfId, newSelfId)) {
- return;
- }
- updateConversationSelfId(newSelfId, true /* notify */);
- }
-
- public void hideAllComposeInputs(final boolean animate) {
- mInputManager.hideAllInputs(animate);
- }
-
- public void saveInputState(final Bundle outState) {
- mInputManager.onSaveInputState(outState);
- }
-
- public void resetMediaPickerState() {
- mInputManager.resetMediaPickerState();
- }
-
- public boolean onBackPressed() {
- return mInputManager.onBackPressed();
- }
-
- public boolean onNavigationUpPressed() {
- return mInputManager.onNavigationUpPressed();
- }
-
- public boolean updateActionBar(final ActionBar actionBar) {
- return mInputManager != null ? mInputManager.updateActionBar(actionBar) : false;
- }
-
- public static boolean shouldShowSimSelector(final ConversationData convData) {
- return OsUtil.isAtLeastL_MR1() &&
- convData.getSelfParticipantsCountExcludingDefault(true /* activeOnly */) > 1;
- }
-
- public void sendMessageIgnoreMessageSizeLimit() {
- sendMessageInternal(false /* checkMessageSize */);
- }
-
- public void onAttachmentPreviewLongClicked() {
- mHost.showAttachmentChooser();
- }
-
- @Override
- public void onDraftAttachmentLoadFailed() {
- mHost.notifyOfAttachmentLoadFailed();
- }
-
- private boolean isOverriddenAvatarAGroup() {
- final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri();
- if (overridenSelfUri == null) {
- return false;
- }
- return AvatarUriUtil.TYPE_GROUP_URI.equals(AvatarUriUtil.getAvatarType(overridenSelfUri));
- }
-
- @Override
- public void setAccessibility(boolean enabled) {
- if (enabled) {
- mAttachMediaButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- mComposeEditText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- mSendButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- setSendButtonAccessibility(mSendWidgetMode);
- } else {
- mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mComposeEditText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mSendButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mAttachMediaButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationActivity.java b/src/com/android/messaging/ui/conversation/ConversationActivity.java
deleted file mode 100644
index 66310ea..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationActivity.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversation;
-
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.text.TextUtils;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.contact.ContactPickerFragment;
-import com.android.messaging.ui.contact.ContactPickerFragment.ContactPickerFragmentHost;
-import com.android.messaging.ui.conversation.ConversationActivityUiState.ConversationActivityUiStateHost;
-import com.android.messaging.ui.conversation.ConversationFragment.ConversationFragmentHost;
-import com.android.messaging.ui.conversationlist.ConversationListActivity;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-public class ConversationActivity extends BugleActionBarActivity
- implements ContactPickerFragmentHost, ConversationFragmentHost,
- ConversationActivityUiStateHost {
- public static final int FINISH_RESULT_CODE = 1;
- private static final String SAVED_INSTANCE_STATE_UI_STATE_KEY = "uistate";
-
- private ConversationActivityUiState mUiState;
-
- // Fragment transactions cannot be performed after onSaveInstanceState() has been called since
- // it will cause state loss. We don't want to call commitAllowingStateLoss() since it's
- // dangerous. Therefore, we note when instance state is saved and avoid performing UI state
- // updates concerning fragments past that point.
- private boolean mInstanceStateSaved;
-
- // Tracks whether onPause is called.
- private boolean mIsPaused;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.conversation_activity);
-
- final Intent intent = getIntent();
-
- // Do our best to restore UI state from saved instance state.
- if (savedInstanceState != null) {
- mUiState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY);
- } else {
- if (intent.
- getBooleanExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, false)) {
- // See the comment in BugleWidgetService.getViewMoreConversationsView() why this
- // is unfortunately necessary. The Bugle desktop widget can display a list of
- // conversations. When there are more conversations that can be displayed in
- // the widget, the last item is a "More conversations" item. The way widgets
- // are built, the list items can only go to a single fill-in intent which points
- // to this ConversationActivity. When the user taps on "More conversations", we
- // really want to go to the ConversationList. This code makes that possible.
- finish();
- final Intent convListIntent = new Intent(this, ConversationListActivity.class);
- convListIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(convListIntent);
- return;
- }
- }
-
- // If saved instance state doesn't offer a clue, get the info from the intent.
- if (mUiState == null) {
- final String conversationId = intent.getStringExtra(
- UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- mUiState = new ConversationActivityUiState(conversationId);
- }
- mUiState.setHost(this);
- mInstanceStateSaved = false;
-
- // Don't animate UI state change for initial setup.
- updateUiState(false /* animate */);
-
- // See if we're getting called from a widget to directly display an image or video
- final String extraToDisplay =
- intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI);
- if (!TextUtils.isEmpty(extraToDisplay)) {
- final String contentType =
- intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE);
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(
- findViewById(R.id.conversation_and_compose_container));
- if (ContentType.isImageType(contentType)) {
- final Uri imagesUri = MessagingContentProvider.buildConversationImagesUri(
- mUiState.getConversationId());
- UIIntents.get().launchFullScreenPhotoViewer(
- this, Uri.parse(extraToDisplay), bounds, imagesUri);
- } else if (ContentType.isVideoType(contentType)) {
- UIIntents.get().launchFullScreenVideoViewer(this, Uri.parse(extraToDisplay));
- }
- }
- }
-
- @Override
- protected void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
- // After onSaveInstanceState() is called, future changes to mUiState won't update the UI
- // anymore, because fragment transactions are not allowed past this point.
- // For an activity recreation due to orientation change, the saved instance state keeps
- // using the in-memory copy of the UI state instead of writing it to parcel as an
- // optimization, so the UI state values may still change in response to, for example,
- // focus change from the framework, making mUiState and actual UI inconsistent.
- // Therefore, save an exact "snapshot" (clone) of the UI state object to make sure the
- // restored UI state ALWAYS matches the actual restored UI components.
- outState.putParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY, mUiState.clone());
- mInstanceStateSaved = true;
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- // we need to reset the mInstanceStateSaved flag since we may have just been restored from
- // a previous onStop() instead of an onDestroy().
- mInstanceStateSaved = false;
- mIsPaused = false;
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mIsPaused = true;
- }
-
- @Override
- public void onWindowFocusChanged(final boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- final ConversationFragment conversationFragment = getConversationFragment();
- // When the screen is turned on, the last used activity gets resumed, but it gets
- // window focus only after the lock screen is unlocked.
- if (hasFocus && conversationFragment != null) {
- conversationFragment.setConversationFocus();
- }
- }
-
- @Override
- public void onDisplayHeightChanged(final int heightSpecification) {
- super.onDisplayHeightChanged(heightSpecification);
- invalidateActionBar();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (mUiState != null) {
- mUiState.setHost(null);
- }
- }
-
- @Override
- public void updateActionBar(final ActionBar actionBar) {
- super.updateActionBar(actionBar);
- final ConversationFragment conversation = getConversationFragment();
- final ContactPickerFragment contactPicker = getContactPicker();
- if (contactPicker != null && mUiState.shouldShowContactPickerFragment()) {
- contactPicker.updateActionBar(actionBar);
- } else if (conversation != null && mUiState.shouldShowConversationFragment()) {
- conversation.updateActionBar(actionBar);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem menuItem) {
- if (super.onOptionsItemSelected(menuItem)) {
- return true;
- }
- if (menuItem.getItemId() == android.R.id.home) {
- onNavigationUpPressed();
- return true;
- }
- return false;
- }
-
- public void onNavigationUpPressed() {
- // Let the conversation fragment handle the navigation up press.
- final ConversationFragment conversationFragment = getConversationFragment();
- if (conversationFragment != null && conversationFragment.onNavigationUpPressed()) {
- return;
- }
- onFinishCurrentConversation();
- }
-
- @Override
- public void onBackPressed() {
- // If action mode is active dismiss it
- if (getActionMode() != null) {
- dismissActionMode();
- return;
- }
-
- // Let the conversation fragment handle the back press.
- final ConversationFragment conversationFragment = getConversationFragment();
- if (conversationFragment != null && conversationFragment.onBackPressed()) {
- return;
- }
- super.onBackPressed();
- }
-
- private ContactPickerFragment getContactPicker() {
- return (ContactPickerFragment) getFragmentManager().findFragmentByTag(
- ContactPickerFragment.FRAGMENT_TAG);
- }
-
- private ConversationFragment getConversationFragment() {
- return (ConversationFragment) getFragmentManager().findFragmentByTag(
- ConversationFragment.FRAGMENT_TAG);
- }
-
- @Override // From ContactPickerFragmentHost
- public void onGetOrCreateNewConversation(final String conversationId) {
- Assert.isTrue(conversationId != null);
- mUiState.onGetOrCreateConversation(conversationId);
- }
-
- @Override // From ContactPickerFragmentHost
- public void onBackButtonPressed() {
- onBackPressed();
- }
-
- @Override // From ContactPickerFragmentHost
- public void onInitiateAddMoreParticipants() {
- mUiState.onAddMoreParticipants();
- }
-
-
- @Override
- public void onParticipantCountChanged(final boolean canAddMoreParticipants) {
- mUiState.onParticipantCountUpdated(canAddMoreParticipants);
- }
-
- @Override // From ConversationFragmentHost
- public void onStartComposeMessage() {
- mUiState.onStartMessageCompose();
- }
-
- @Override // From ConversationFragmentHost
- public void onConversationMetadataUpdated() {
- invalidateActionBar();
- }
-
- @Override // From ConversationFragmentHost
- public void onConversationMessagesUpdated(final int numberOfMessages) {
- }
-
- @Override // From ConversationFragmentHost
- public void onConversationParticipantDataLoaded(final int numberOfParticipants) {
- }
-
- @Override // From ConversationFragmentHost
- public boolean isActiveAndFocused() {
- return !mIsPaused && hasWindowFocus();
- }
-
- @Override // From ConversationActivityUiStateListener
- public void onConversationContactPickerUiStateChanged(final int oldState, final int newState,
- final boolean animate) {
- Assert.isTrue(oldState != newState);
- updateUiState(animate);
- }
-
- private void updateUiState(final boolean animate) {
- if (mInstanceStateSaved || mIsPaused) {
- return;
- }
- Assert.notNull(mUiState);
- final Intent intent = getIntent();
- final String conversationId = mUiState.getConversationId();
-
- final FragmentManager fragmentManager = getFragmentManager();
- final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
-
- final boolean needConversationFragment = mUiState.shouldShowConversationFragment();
- final boolean needContactPickerFragment = mUiState.shouldShowContactPickerFragment();
- ConversationFragment conversationFragment = getConversationFragment();
-
- // Set up the conversation fragment.
- if (needConversationFragment) {
- Assert.notNull(conversationId);
- if (conversationFragment == null) {
- conversationFragment = new ConversationFragment();
- fragmentTransaction.add(R.id.conversation_fragment_container,
- conversationFragment, ConversationFragment.FRAGMENT_TAG);
- }
- final MessageData draftData = intent.getParcelableExtra(
- UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
- if (!needContactPickerFragment) {
- // Once the user has committed the audience,remove the draft data from the
- // intent to prevent reuse
- intent.removeExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
- }
- conversationFragment.setHost(this);
- conversationFragment.setConversationInfo(this, conversationId, draftData);
- } else if (conversationFragment != null) {
- // Don't save draft to DB when removing conversation fragment and switching to
- // contact picking mode. The draft is intended for the new group.
- conversationFragment.suppressWriteDraft();
- fragmentTransaction.remove(conversationFragment);
- }
-
- // Set up the contact picker fragment.
- ContactPickerFragment contactPickerFragment = getContactPicker();
- if (needContactPickerFragment) {
- if (contactPickerFragment == null) {
- contactPickerFragment = new ContactPickerFragment();
- fragmentTransaction.add(R.id.contact_picker_fragment_container,
- contactPickerFragment, ContactPickerFragment.FRAGMENT_TAG);
- }
- contactPickerFragment.setHost(this);
- contactPickerFragment.setContactPickingMode(mUiState.getDesiredContactPickingMode(),
- animate);
- } else if (contactPickerFragment != null) {
- fragmentTransaction.remove(contactPickerFragment);
- }
-
- fragmentTransaction.commit();
- invalidateActionBar();
- }
-
- @Override
- public void onFinishCurrentConversation() {
- // Simply finish the current activity. The current design is to leave any empty
- // conversations as is.
- if (OsUtil.isAtLeastL()) {
- finishAfterTransition();
- } else {
- finish();
- }
- }
-
- @Override
- public boolean shouldResumeComposeMessage() {
- return mUiState.shouldResumeComposeMessage();
- }
-
- @Override
- protected void onActivityResult(final int requestCode, final int resultCode,
- final Intent data) {
- if (requestCode == ConversationFragment.REQUEST_CHOOSE_ATTACHMENTS &&
- resultCode == RESULT_OK) {
- final ConversationFragment conversationFragment = getConversationFragment();
- if (conversationFragment != null) {
- conversationFragment.onAttachmentChoosen();
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG, "ConversationFragment is missing after launching " +
- "AttachmentChooserActivity!");
- }
- } else if (resultCode == FINISH_RESULT_CODE) {
- finish();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java b/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java
deleted file mode 100644
index 1469c93..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.ui.contact.ContactPickerFragment;
-import com.android.messaging.util.Assert;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Keeps track of the different UI states that the ConversationActivity may be in. This acts as
- * a state machine which, based on different actions (e.g. onAddMoreParticipants), notifies the
- * ConversationActivity about any state UI change so it can update the visuals. This class
- * implements Parcelable and it's persisted across activity tear down and relaunch.
- */
-public class ConversationActivityUiState implements Parcelable, Cloneable {
- interface ConversationActivityUiStateHost {
- void onConversationContactPickerUiStateChanged(int oldState, int newState, boolean animate);
- }
-
- /*------ Overall UI states (conversation & contact picker) ------*/
-
- /** Only a full screen conversation is showing. */
- public static final int STATE_CONVERSATION_ONLY = 1;
- /** Only a full screen contact picker is showing asking user to pick the initial contact. */
- public static final int STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT = 2;
- /**
- * Only a full screen contact picker is showing asking user to pick more participants. This
- * happens after the user picked the initial contact, and then decide to go back and add more.
- */
- public static final int STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS = 3;
- /**
- * Only a full screen contact picker is showing asking user to pick more participants. However
- * user has reached max number of conversation participants and can add no more.
- */
- public static final int STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS = 4;
- /**
- * A hybrid mode where the conversation view + contact chips view are showing. This happens
- * right after the user picked the initial contact for which a 1-1 conversation is fetched or
- * created.
- */
- public static final int STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW = 5;
-
- // The overall UI state of the ConversationActivity.
- private int mConversationContactUiState;
-
- // The currently displayed conversation (if any).
- private String mConversationId;
-
- // Indicates whether we should put focus in the compose message view when the
- // ConversationFragment is attached. This is a transient state that's not persisted as
- // part of the parcelable.
- private boolean mPendingResumeComposeMessage = false;
-
- // The owner ConversationActivity. This is not parceled since the instance always change upon
- // object reuse.
- private ConversationActivityUiStateHost mHost;
-
- // Indicates the owning ConverastionActivity is in the process of updating its UI presentation
- // to be in sync with the UI states. Outside of the UI updates, the UI states here should
- // ALWAYS be consistent with the actual states of the activity.
- private int mUiUpdateCount;
-
- /**
- * Create a new instance with an initial conversation id.
- */
- ConversationActivityUiState(final String conversationId) {
- // The conversation activity may be initialized with only one of two states:
- // Conversation-only (when there's a conversation id) or picking initial contact
- // (when no conversation id is given).
- mConversationId = conversationId;
- mConversationContactUiState = conversationId == null ?
- STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT : STATE_CONVERSATION_ONLY;
- }
-
- public void setHost(final ConversationActivityUiStateHost host) {
- mHost = host;
- }
-
- public boolean shouldShowConversationFragment() {
- return mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW ||
- mConversationContactUiState == STATE_CONVERSATION_ONLY;
- }
-
- public boolean shouldShowContactPickerFragment() {
- return mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS ||
- mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS ||
- mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT ||
- mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW;
- }
-
- /**
- * Returns whether there's a pending request to resume message compose (i.e. set focus to
- * the compose message view and show the soft keyboard). If so, this request will be served
- * when the conversation fragment get created and resumed. This happens when the user commits
- * participant selection for a group conversation and goes back to the conversation fragment.
- * Since conversation fragment creation happens asynchronously, we issue and track this
- * pending request for it to be eventually fulfilled.
- */
- public boolean shouldResumeComposeMessage() {
- if (mPendingResumeComposeMessage) {
- // This is a one-shot operation that just keeps track of the pending resume compose
- // state. This is also a non-critical operation so we don't care about failure case.
- mPendingResumeComposeMessage = false;
- return true;
- }
- return false;
- }
-
- public int getDesiredContactPickingMode() {
- switch (mConversationContactUiState) {
- case STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS:
- return ContactPickerFragment.MODE_PICK_MORE_CONTACTS;
- case STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS:
- return ContactPickerFragment.MODE_PICK_MAX_PARTICIPANTS;
- case STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT:
- return ContactPickerFragment.MODE_PICK_INITIAL_CONTACT;
- case STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW:
- return ContactPickerFragment.MODE_CHIPS_ONLY;
- default:
- Assert.fail("Invalid contact picking mode for ConversationActivity!");
- return ContactPickerFragment.MODE_UNDEFINED;
- }
- }
-
- public String getConversationId() {
- return mConversationId;
- }
-
- /**
- * Called whenever the contact picker fragment successfully fetched or created a conversation.
- */
- public void onGetOrCreateConversation(final String conversationId) {
- int newState = STATE_CONVERSATION_ONLY;
- if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) {
- newState = STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW;
- } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS ||
- mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS) {
- newState = STATE_CONVERSATION_ONLY;
- } else {
- // New conversation should only be created when we are in one of the contact picking
- // modes.
- Assert.fail("Invalid conversation activity state: can't create conversation!");
- }
- mConversationId = conversationId;
- performUiStateUpdate(newState, true);
- }
-
- /**
- * Called when the user started composing message. If we are in the hybrid chips state, we
- * should commit to enter the conversation only state.
- */
- public void onStartMessageCompose() {
- // This cannot happen when we are in one of the full-screen contact picking states.
- Assert.isTrue(mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT &&
- mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS &&
- mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS);
- if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) {
- performUiStateUpdate(STATE_CONVERSATION_ONLY, true);
- }
- }
-
- /**
- * Called when the user initiated an action to add more participants in the hybrid state,
- * namely clicking on the "add more participants" button or entered a new contact chip via
- * auto-complete.
- */
- public void onAddMoreParticipants() {
- if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) {
- mPendingResumeComposeMessage = true;
- performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, true);
- } else {
- // This is only possible in the hybrid state.
- Assert.fail("Invalid conversation activity state: can't add more participants!");
- }
- }
-
- /**
- * Called each time the number of participants is updated to check against the limit and
- * update the ui state accordingly.
- */
- public void onParticipantCountUpdated(final boolean canAddMoreParticipants) {
- if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS
- && !canAddMoreParticipants) {
- performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS, false);
- } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS
- && canAddMoreParticipants) {
- performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, false);
- }
- }
-
- private void performUiStateUpdate(final int conversationContactState, final boolean animate) {
- // This starts one UI update cycle, during which we allow the conversation activity's
- // UI presentation to be temporarily out of sync with the states here.
- beginUiUpdate();
-
- if (conversationContactState != mConversationContactUiState) {
- final int oldState = mConversationContactUiState;
- mConversationContactUiState = conversationContactState;
- notifyOnOverallUiStateChanged(oldState, mConversationContactUiState, animate);
- }
- endUiUpdate();
- }
-
- private void notifyOnOverallUiStateChanged(
- final int oldState, final int newState, final boolean animate) {
- // Always verify state validity whenever we have a state change.
- assertValidState();
- Assert.isTrue(isUiUpdateInProgress());
-
- // Only do this if we are still attached to the host. mHost can be null if the host
- // activity is already destroyed, but due to timing the contained UI components may still
- // receive events such as focus change and trigger a callback to the Ui state. We'd like
- // to guard against those cases.
- if (mHost != null) {
- mHost.onConversationContactPickerUiStateChanged(oldState, newState, animate);
- }
- }
-
- private void assertValidState() {
- // Conversation id may be null IF AND ONLY IF the user is picking the initial contact to
- // start a conversation.
- Assert.isTrue((mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) ==
- (mConversationId == null));
- }
-
- private void beginUiUpdate() {
- mUiUpdateCount++;
- }
-
- private void endUiUpdate() {
- if (--mUiUpdateCount < 0) {
- Assert.fail("Unbalanced Ui updates!");
- }
- }
-
- private boolean isUiUpdateInProgress() {
- return mUiUpdateCount > 0;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeInt(mConversationContactUiState);
- dest.writeString(mConversationId);
- }
-
- private ConversationActivityUiState(final Parcel in) {
- mConversationContactUiState = in.readInt();
- mConversationId = in.readString();
-
- // Always verify state validity whenever we initialize states.
- assertValidState();
- }
-
- public static final Parcelable.Creator<ConversationActivityUiState> CREATOR
- = new Parcelable.Creator<ConversationActivityUiState>() {
- @Override
- public ConversationActivityUiState createFromParcel(final Parcel in) {
- return new ConversationActivityUiState(in);
- }
-
- @Override
- public ConversationActivityUiState[] newArray(final int size) {
- return new ConversationActivityUiState[size];
- }
- };
-
- @Override
- protected ConversationActivityUiState clone() {
- try {
- return (ConversationActivityUiState) super.clone();
- } catch (CloneNotSupportedException e) {
- Assert.fail("ConversationActivityUiState: failed to clone(). Is there a mutable " +
- "reference?");
- }
- return null;
- }
-
- /**
- * allows for overridding the internal UI state. Should never be called except by test code.
- */
- @VisibleForTesting
- void testSetUiState(final int uiState) {
- mConversationContactUiState = uiState;
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationFastScroller.java b/src/com/android/messaging/ui/conversation/ConversationFastScroller.java
deleted file mode 100644
index b15f05a..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationFastScroller.java
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversation;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.graphics.drawable.StateListDrawable;
-import android.os.Handler;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.AdapterDataObserver;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.util.StateSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroupOverlay;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Adds a "fast-scroll" bar to the conversation RecyclerView that shows the current position within
- * the conversation and allows quickly moving to another position by dragging the scrollbar thumb
- * up or down. As the thumb is dragged, we show a floating bubble alongside it that shows the
- * date/time of the first visible message at the current position.
- */
-public class ConversationFastScroller extends RecyclerView.OnScrollListener implements
- OnLayoutChangeListener, RecyclerView.OnItemTouchListener {
-
- /**
- * Creates a {@link ConversationFastScroller} instance, attached to the provided
- * {@link RecyclerView}.
- *
- * @param rv the conversation RecyclerView
- * @param position where the scrollbar should appear (either {@code POSITION_RIGHT_SIDE} or
- * {@code POSITION_LEFT_SIDE})
- * @return a new ConversationFastScroller, or {@code null} if fast-scrolling is not supported
- * (the feature requires Jellybean MR2 or newer)
- */
- public static ConversationFastScroller addTo(RecyclerView rv, int position) {
- if (OsUtil.isAtLeastJB_MR2()) {
- return new ConversationFastScroller(rv, position);
- }
- return null;
- }
-
- public static final int POSITION_RIGHT_SIDE = 0;
- public static final int POSITION_LEFT_SIDE = 1;
-
- private static final int MIN_PAGES_TO_ENABLE = 7;
- private static final int SHOW_ANIMATION_DURATION_MS = 150;
- private static final int HIDE_ANIMATION_DURATION_MS = 300;
- private static final int HIDE_DELAY_MS = 1500;
-
- private final Context mContext;
- private final RecyclerView mRv;
- private final ViewGroupOverlay mOverlay;
- private final ImageView mTrackImageView;
- private final ImageView mThumbImageView;
- private final TextView mPreviewTextView;
-
- private final int mTrackWidth;
- private final int mThumbHeight;
- private final int mPreviewHeight;
- private final int mPreviewMinWidth;
- private final int mPreviewMarginTop;
- private final int mPreviewMarginLeftRight;
- private final int mTouchSlop;
-
- private final Rect mContainer = new Rect();
- private final Handler mHandler = new Handler();
-
- // Whether to render the scrollbar on the right side (otherwise it'll be on the left).
- private final boolean mPosRight;
-
- // Whether the scrollbar is currently visible (it may still be animating).
- private boolean mVisible = false;
-
- // Whether we are waiting to hide the scrollbar (i.e. scrolling has stopped).
- private boolean mPendingHide = false;
-
- // Whether the user is currently dragging the thumb up or down.
- private boolean mDragging = false;
-
- // Animations responsible for hiding the scrollbar & preview. May be null.
- private AnimatorSet mHideAnimation;
- private ObjectAnimator mHidePreviewAnimation;
-
- private final Runnable mHideTrackRunnable = new Runnable() {
- @Override
- public void run() {
- hide(true /* animate */);
- mPendingHide = false;
- }
- };
-
- private ConversationFastScroller(RecyclerView rv, int position) {
- mContext = rv.getContext();
- mRv = rv;
- mRv.addOnLayoutChangeListener(this);
- mRv.addOnScrollListener(this);
- mRv.addOnItemTouchListener(this);
- mRv.getAdapter().registerAdapterDataObserver(new AdapterDataObserver() {
- @Override
- public void onChanged() {
- updateScrollPos();
- }
- });
- mPosRight = (position == POSITION_RIGHT_SIDE);
-
- // Cache the dimensions we'll need during layout
- final Resources res = mContext.getResources();
- mTrackWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_width);
- mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height);
- mPreviewHeight = res.getDimensionPixelSize(R.dimen.fastscroll_preview_height);
- mPreviewMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_preview_min_width);
- mPreviewMarginTop = res.getDimensionPixelOffset(R.dimen.fastscroll_preview_margin_top);
- mPreviewMarginLeftRight = res.getDimensionPixelOffset(
- R.dimen.fastscroll_preview_margin_left_right);
- mTouchSlop = res.getDimensionPixelOffset(R.dimen.fastscroll_touch_slop);
-
- final LayoutInflater inflator = LayoutInflater.from(mContext);
- mTrackImageView = (ImageView) inflator.inflate(R.layout.fastscroll_track, null);
- mThumbImageView = (ImageView) inflator.inflate(R.layout.fastscroll_thumb, null);
- mPreviewTextView = (TextView) inflator.inflate(R.layout.fastscroll_preview, null);
-
- refreshConversationThemeColor();
-
- // Add the fast scroll views to the overlay, so they are rendered above the list
- mOverlay = rv.getOverlay();
- mOverlay.add(mTrackImageView);
- mOverlay.add(mThumbImageView);
- mOverlay.add(mPreviewTextView);
-
- hide(false /* animate */);
- mPreviewTextView.setAlpha(0f);
- }
-
- public void refreshConversationThemeColor() {
- mPreviewTextView.setBackground(
- ConversationDrawables.get().getFastScrollPreviewDrawable(mPosRight));
- if (OsUtil.isAtLeastL()) {
- final StateListDrawable drawable = new StateListDrawable();
- drawable.addState(new int[]{ android.R.attr.state_pressed },
- ConversationDrawables.get().getFastScrollThumbDrawable(true /* pressed */));
- drawable.addState(StateSet.WILD_CARD,
- ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */));
- mThumbImageView.setImageDrawable(drawable);
- } else {
- // Android pre-L doesn't seem to handle a StateListDrawable containing a tinted
- // drawable (it's rendered in the filter base color, which is red), so fall back to
- // just the regular (non-pressed) drawable.
- mThumbImageView.setImageDrawable(
- ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */));
- }
- }
-
- @Override
- public void onScrollStateChanged(final RecyclerView view, final int newState) {
- if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
- // Only show the scrollbar once the user starts scrolling
- if (!mVisible && isEnabled()) {
- show();
- }
- cancelAnyPendingHide();
- } else if (newState == RecyclerView.SCROLL_STATE_IDLE && !mDragging) {
- // Hide the scrollbar again after scrolling stops
- hideAfterDelay();
- }
- }
-
- private boolean isEnabled() {
- final int range = mRv.computeVerticalScrollRange();
- final int extent = mRv.computeVerticalScrollExtent();
-
- if (range == 0 || extent == 0) {
- return false; // Conversation isn't long enough to scroll
- }
- // Only enable scrollbars for conversations long enough that they would require several
- // flings to scroll through.
- final float pages = (float) range / extent;
- return (pages > MIN_PAGES_TO_ENABLE);
- }
-
- private void show() {
- if (mHideAnimation != null && mHideAnimation.isRunning()) {
- mHideAnimation.cancel();
- }
- // Slide the scrollbar in from the side
- ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X, 0);
- ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X, 0);
- AnimatorSet animation = new AnimatorSet();
- animation.playTogether(trackSlide, thumbSlide);
- animation.setDuration(SHOW_ANIMATION_DURATION_MS);
- animation.start();
-
- mVisible = true;
- updateScrollPos();
- }
-
- private void hideAfterDelay() {
- cancelAnyPendingHide();
- mHandler.postDelayed(mHideTrackRunnable, HIDE_DELAY_MS);
- mPendingHide = true;
- }
-
- private void cancelAnyPendingHide() {
- if (mPendingHide) {
- mHandler.removeCallbacks(mHideTrackRunnable);
- }
- }
-
- private void hide(boolean animate) {
- final int hiddenTranslationX = mPosRight ? mTrackWidth : -mTrackWidth;
- if (animate) {
- // Slide the scrollbar off to the side
- ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X,
- hiddenTranslationX);
- ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X,
- hiddenTranslationX);
- mHideAnimation = new AnimatorSet();
- mHideAnimation.playTogether(trackSlide, thumbSlide);
- mHideAnimation.setDuration(HIDE_ANIMATION_DURATION_MS);
- mHideAnimation.start();
- } else {
- mTrackImageView.setTranslationX(hiddenTranslationX);
- mThumbImageView.setTranslationX(hiddenTranslationX);
- }
-
- mVisible = false;
- }
-
- private void showPreview() {
- if (mHidePreviewAnimation != null && mHidePreviewAnimation.isRunning()) {
- mHidePreviewAnimation.cancel();
- }
- mPreviewTextView.setAlpha(1f);
- }
-
- private void hidePreview() {
- mHidePreviewAnimation = ObjectAnimator.ofFloat(mPreviewTextView, View.ALPHA, 0f);
- mHidePreviewAnimation.setDuration(HIDE_ANIMATION_DURATION_MS);
- mHidePreviewAnimation.start();
- }
-
- @Override
- public void onScrolled(final RecyclerView view, final int dx, final int dy) {
- updateScrollPos();
- }
-
- private void updateScrollPos() {
- if (!mVisible) {
- return;
- }
- final int verticalScrollLength = mContainer.height() - mThumbHeight;
- final int verticalScrollStart = mContainer.top + mThumbHeight / 2;
-
- final float scrollRatio = computeScrollRatio();
- final int thumbCenterY = verticalScrollStart + (int)(verticalScrollLength * scrollRatio);
- layoutThumb(thumbCenterY);
-
- if (mDragging) {
- updatePreviewText();
- layoutPreview(thumbCenterY);
- }
- }
-
- /**
- * Returns the current position in the conversation, as a value between 0 and 1, inclusive.
- * The top of the conversation is 0, the bottom is 1, the exact middle is 0.5, and so on.
- */
- private float computeScrollRatio() {
- final int range = mRv.computeVerticalScrollRange();
- final int extent = mRv.computeVerticalScrollExtent();
- int offset = mRv.computeVerticalScrollOffset();
-
- if (range == 0 || extent == 0) {
- // If the conversation doesn't scroll, we're at the bottom.
- return 1.0f;
- }
- final int scrollRange = range - extent;
- offset = Math.min(offset, scrollRange);
- return offset / (float) scrollRange;
- }
-
- private void updatePreviewText() {
- final LinearLayoutManager lm = (LinearLayoutManager) mRv.getLayoutManager();
- final int pos = lm.findFirstVisibleItemPosition();
- if (pos == RecyclerView.NO_POSITION) {
- return;
- }
- final ViewHolder vh = mRv.findViewHolderForAdapterPosition(pos);
- if (vh == null) {
- // This can happen if the messages update while we're dragging the thumb.
- return;
- }
- final ConversationMessageView messageView = (ConversationMessageView) vh.itemView;
- final ConversationMessageData messageData = messageView.getData();
- final long timestamp = messageData.getReceivedTimeStamp();
- final CharSequence timestampText = Dates.getFastScrollPreviewTimeString(timestamp);
- mPreviewTextView.setText(timestampText);
- }
-
- @Override
- public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
- if (!mVisible) {
- return false;
- }
- // If the user presses down on the scroll thumb, we'll start intercepting events from the
- // RecyclerView so we can handle the move events while they're dragging it up/down.
- final int action = e.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- if (isInsideThumb(e.getX(), e.getY())) {
- startDrag();
- return true;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (mDragging) {
- return true;
- }
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- if (mDragging) {
- cancelDrag();
- }
- return false;
- }
- return false;
- }
-
- private boolean isInsideThumb(float x, float y) {
- final int hitTargetLeft = mThumbImageView.getLeft() - mTouchSlop;
- final int hitTargetRight = mThumbImageView.getRight() + mTouchSlop;
-
- if (x < hitTargetLeft || x > hitTargetRight) {
- return false;
- }
- if (y < mThumbImageView.getTop() || y > mThumbImageView.getBottom()) {
- return false;
- }
- return true;
- }
-
- @Override
- public void onTouchEvent(RecyclerView rv, MotionEvent e) {
- if (!mDragging) {
- return;
- }
- final int action = e.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- handleDragMove(e.getY());
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- cancelDrag();
- break;
- }
- }
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- }
-
- private void startDrag() {
- mDragging = true;
- mThumbImageView.setPressed(true);
- updateScrollPos();
- showPreview();
- cancelAnyPendingHide();
- }
-
- private void handleDragMove(float y) {
- final int verticalScrollLength = mContainer.height() - mThumbHeight;
- final int verticalScrollStart = mContainer.top + (mThumbHeight / 2);
-
- // Convert the desired position from px to a scroll position in the conversation.
- float dragScrollRatio = (y - verticalScrollStart) / verticalScrollLength;
- dragScrollRatio = Math.max(dragScrollRatio, 0.0f);
- dragScrollRatio = Math.min(dragScrollRatio, 1.0f);
-
- // Scroll the RecyclerView to a new position.
- final int itemCount = mRv.getAdapter().getItemCount();
- final int itemPos = (int)((itemCount - 1) * dragScrollRatio);
- mRv.scrollToPosition(itemPos);
- }
-
- private void cancelDrag() {
- mDragging = false;
- mThumbImageView.setPressed(false);
- hidePreview();
- hideAfterDelay();
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- if (!mVisible) {
- hide(false /* animate */);
- }
- // The container is the size of the RecyclerView that's visible on screen. We have to
- // exclude the top padding, because it's usually hidden behind the conversation action bar.
- mContainer.set(left, top + mRv.getPaddingTop(), right, bottom);
- layoutTrack();
- updateScrollPos();
- }
-
- private void layoutTrack() {
- int trackHeight = Math.max(0, mContainer.height());
- int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY);
- int heightMeasureSpec = MeasureSpec.makeMeasureSpec(trackHeight, MeasureSpec.EXACTLY);
- mTrackImageView.measure(widthMeasureSpec, heightMeasureSpec);
-
- int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left;
- int top = mContainer.top;
- int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth);
- int bottom = mContainer.bottom;
- mTrackImageView.layout(left, top, right, bottom);
- }
-
- private void layoutThumb(int centerY) {
- int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY);
- int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mThumbHeight, MeasureSpec.EXACTLY);
- mThumbImageView.measure(widthMeasureSpec, heightMeasureSpec);
-
- int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left;
- int top = centerY - (mThumbImageView.getHeight() / 2);
- int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth);
- int bottom = top + mThumbHeight;
- mThumbImageView.layout(left, top, right, bottom);
- }
-
- private void layoutPreview(int centerY) {
- int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mContainer.width(), MeasureSpec.AT_MOST);
- int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewHeight, MeasureSpec.EXACTLY);
- mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec);
-
- // Ensure that the preview bubble is at least as wide as it is tall
- if (mPreviewTextView.getMeasuredWidth() < mPreviewMinWidth) {
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewMinWidth, MeasureSpec.EXACTLY);
- mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec);
- }
- final int previewMinY = mContainer.top + mPreviewMarginTop;
-
- final int left, right;
- if (mPosRight) {
- right = mContainer.right - mTrackWidth - mPreviewMarginLeftRight;
- left = right - mPreviewTextView.getMeasuredWidth();
- } else {
- left = mContainer.left + mTrackWidth + mPreviewMarginLeftRight;
- right = left + mPreviewTextView.getMeasuredWidth();
- }
-
- int bottom = centerY;
- int top = bottom - mPreviewTextView.getMeasuredHeight();
- if (top < previewMinY) {
- top = previewMinY;
- bottom = top + mPreviewTextView.getMeasuredHeight();
- }
- mPreviewTextView.layout(left, top, right, bottom);
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java
deleted file mode 100644
index a6a191a..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationFragment.java
+++ /dev/null
@@ -1,1662 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversation;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.DownloadManager;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.database.Cursor;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Parcelable;
-import android.support.v4.content.LocalBroadcastManager;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.support.v7.app.ActionBar;
-import android.support.v7.widget.DefaultItemAnimator;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.text.TextUtils;
-import android.view.ActionMode;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.action.InsertNewMessageAction;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.ConversationData;
-import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.ConversationParticipantsData;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.ui.AttachmentPreview;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.ui.SnackBar;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.animation.PopupTransitionAnimation;
-import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
-import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageViewHost;
-import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputHost;
-import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost;
-import com.android.messaging.ui.mediapicker.MediaPicker;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ChangeDefaultSmsAppHelper;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.TextUtil;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows a list of messages/parts comprising a conversation.
- */
-public class ConversationFragment extends Fragment implements ConversationDataListener,
- IComposeMessageViewHost, ConversationMessageViewHost, ConversationInputHost,
- DraftMessageDataListener {
-
- public interface ConversationFragmentHost extends ImeUtil.ImeStateHost {
- void onStartComposeMessage();
- void onConversationMetadataUpdated();
- boolean shouldResumeComposeMessage();
- void onFinishCurrentConversation();
- void invalidateActionBar();
- ActionMode startActionMode(ActionMode.Callback callback);
- void dismissActionMode();
- ActionMode getActionMode();
- void onConversationMessagesUpdated(int numberOfMessages);
- void onConversationParticipantDataLoaded(int numberOfParticipants);
- boolean isActiveAndFocused();
- }
-
- public static final String FRAGMENT_TAG = "conversation";
-
- static final int REQUEST_CHOOSE_ATTACHMENTS = 2;
- private static final int JUMP_SCROLL_THRESHOLD = 15;
- // We animate the message from draft to message list, if we the message doesn't show up in the
- // list within this time limit, then we just do a fade in animation instead
- public static final int MESSAGE_ANIMATION_MAX_WAIT = 500;
-
- private ComposeMessageView mComposeMessageView;
- private RecyclerView mRecyclerView;
- private ConversationMessageAdapter mAdapter;
- private ConversationFastScroller mFastScroller;
-
- private View mConversationComposeDivider;
- private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper;
-
- private String mConversationId;
- // If the fragment receives a draft as part of the invocation this is set
- private MessageData mIncomingDraft;
-
- // This binding keeps track of our associated ConversationData instance
- // A binding should have the lifetime of the owning component,
- // don't recreate, unbind and bind if you need new data
- @VisibleForTesting
- final Binding<ConversationData> mBinding = BindingBase.createBinding(this);
-
- // Saved Instance State Data - only for temporal data which is nice to maintain but not
- // critical for correctness.
- private static final String SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY = "conversationViewState";
- private Parcelable mListState;
-
- private ConversationFragmentHost mHost;
-
- protected List<Integer> mFilterResults;
-
- // The minimum scrolling distance between RecyclerView's scroll change event beyong which
- // a fling motion is considered fast, in which case we'll delay load image attachments for
- // perf optimization.
- private int mFastFlingThreshold;
-
- // ConversationMessageView that is currently selected
- private ConversationMessageView mSelectedMessage;
-
- // Attachment data for the attachment within the selected message that was long pressed
- private MessagePartData mSelectedAttachment;
-
- // Normally, as soon as draft message is loaded, we trust the UI state held in
- // ComposeMessageView to be the only source of truth (incl. the conversation self id). However,
- // there can be external events that forces the UI state to change, such as SIM state changes
- // or SIM auto-switching on receiving a message. This receiver is used to receive such
- // local broadcast messages and reflect the change in the UI.
- private final BroadcastReceiver mConversationSelfIdChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- final String conversationId =
- intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- final String selfId =
- intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_SELF_ID);
- Assert.notNull(conversationId);
- Assert.notNull(selfId);
- if (TextUtils.equals(mBinding.getData().getConversationId(), conversationId)) {
- mComposeMessageView.updateConversationSelfIdOnExternalChange(selfId);
- }
- }
- };
-
- // Flag to prevent writing draft to DB on pause
- private boolean mSuppressWriteDraft;
-
- // Indicates whether local draft should be cleared due to external draft changes that must
- // be reloaded from db
- private boolean mClearLocalDraft;
- private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
-
- private boolean isScrolledToBottom() {
- if (mRecyclerView.getChildCount() == 0) {
- return true;
- }
- final View lastView = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1);
- int lastVisibleItem = ((LinearLayoutManager) mRecyclerView
- .getLayoutManager()).findLastVisibleItemPosition();
- if (lastVisibleItem < 0) {
- // If the recyclerView height is 0, then the last visible item position is -1
- // Try to compute the position of the last item, even though it's not visible
- final long id = mRecyclerView.getChildItemId(lastView);
- final RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForItemId(id);
- if (holder != null) {
- lastVisibleItem = holder.getAdapterPosition();
- }
- }
- final int totalItemCount = mRecyclerView.getAdapter().getItemCount();
- final boolean isAtBottom = (lastVisibleItem + 1 == totalItemCount);
- return isAtBottom && lastView.getBottom() <= mRecyclerView.getHeight();
- }
-
- private void scrollToBottom(final boolean smoothScroll) {
- if (mAdapter.getItemCount() > 0) {
- scrollToPosition(mAdapter.getItemCount() - 1, smoothScroll);
- }
- }
-
- private int mScrollToDismissThreshold;
- private final RecyclerView.OnScrollListener mListScrollListener =
- new RecyclerView.OnScrollListener() {
- // Keeps track of cumulative scroll delta during a scroll event, which we may use to
- // hide the media picker & co.
- private int mCumulativeScrollDelta;
- private boolean mScrollToDismissHandled;
- private boolean mWasScrolledToBottom = true;
- private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
-
- @Override
- public void onScrollStateChanged(final RecyclerView view, final int newState) {
- if (newState == RecyclerView.SCROLL_STATE_IDLE) {
- // Reset scroll states.
- mCumulativeScrollDelta = 0;
- mScrollToDismissHandled = false;
- } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
- mRecyclerView.getItemAnimator().endAnimations();
- }
- mScrollState = newState;
- }
-
- @Override
- public void onScrolled(final RecyclerView view, final int dx, final int dy) {
- if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING &&
- !mScrollToDismissHandled) {
- mCumulativeScrollDelta += dy;
- // Dismiss the keyboard only when the user scroll up (into the past).
- if (mCumulativeScrollDelta < -mScrollToDismissThreshold) {
- mComposeMessageView.hideAllComposeInputs(false /* animate */);
- mScrollToDismissHandled = true;
- }
- }
- if (mWasScrolledToBottom != isScrolledToBottom()) {
- mConversationComposeDivider.animate().alpha(isScrolledToBottom() ? 0 : 1);
- mWasScrolledToBottom = isScrolledToBottom();
- }
- }
- };
-
- private final ActionMode.Callback mMessageActionModeCallback = new ActionMode.Callback() {
- @Override
- public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
- if (mSelectedMessage == null) {
- return false;
- }
- final ConversationMessageData data = mSelectedMessage.getData();
- final MenuInflater menuInflater = getActivity().getMenuInflater();
- menuInflater.inflate(R.menu.conversation_fragment_select_menu, menu);
- menu.findItem(R.id.action_download).setVisible(data.getShowDownloadMessage());
- menu.findItem(R.id.action_send).setVisible(data.getShowResendMessage());
-
- // ShareActionProvider does not work with ActionMode. So we use a normal menu item.
- menu.findItem(R.id.share_message_menu).setVisible(data.getCanForwardMessage());
- menu.findItem(R.id.save_attachment).setVisible(mSelectedAttachment != null);
- menu.findItem(R.id.forward_message_menu).setVisible(data.getCanForwardMessage());
-
- // TODO: We may want to support copying attachments in the future, but it's
- // unclear which attachment to pick when we make this context menu at the message level
- // instead of the part level
- menu.findItem(R.id.copy_text).setVisible(data.getCanCopyMessageToClipboard());
-
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
- final ConversationMessageData data = mSelectedMessage.getData();
- final String messageId = data.getMessageId();
- switch (menuItem.getItemId()) {
- case R.id.save_attachment:
- if (OsUtil.hasStoragePermission()) {
- final SaveAttachmentTask saveAttachmentTask = new SaveAttachmentTask(
- getActivity());
- for (final MessagePartData part : data.getAttachments()) {
- saveAttachmentTask.addAttachmentToSave(part.getContentUri(),
- part.getContentType());
- }
- if (saveAttachmentTask.getAttachmentCount() > 0) {
- saveAttachmentTask.executeOnThreadPool();
- mHost.dismissActionMode();
- }
- } else {
- getActivity().requestPermissions(
- new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);
- }
- return true;
- case R.id.action_delete_message:
- if (mSelectedMessage != null) {
- deleteMessage(messageId);
- }
- return true;
- case R.id.action_download:
- if (mSelectedMessage != null) {
- retryDownload(messageId);
- mHost.dismissActionMode();
- }
- return true;
- case R.id.action_send:
- if (mSelectedMessage != null) {
- retrySend(messageId);
- mHost.dismissActionMode();
- }
- return true;
- case R.id.copy_text:
- Assert.isTrue(data.hasText());
- final ClipboardManager clipboard = (ClipboardManager) getActivity()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- clipboard.setPrimaryClip(
- ClipData.newPlainText(null /* label */, data.getText()));
- mHost.dismissActionMode();
- return true;
- case R.id.details_menu:
- MessageDetailsDialog.show(
- getActivity(), data, mBinding.getData().getParticipants(),
- mBinding.getData().getSelfParticipantById(data.getSelfParticipantId()));
- mHost.dismissActionMode();
- return true;
- case R.id.share_message_menu:
- shareMessage(data);
- mHost.dismissActionMode();
- return true;
- case R.id.forward_message_menu:
- // TODO: Currently we are forwarding one part at a time, instead of
- // the entire message. Change this to forwarding the entire message when we
- // use message-based cursor in conversation.
- final MessageData message = mBinding.getData().createForwardedMessage(data);
- UIIntents.get().launchForwardMessageActivity(getActivity(), message);
- mHost.dismissActionMode();
- return true;
- }
- return false;
- }
-
- private void shareMessage(final ConversationMessageData data) {
- // Figure out what to share.
- MessagePartData attachmentToShare = mSelectedAttachment;
- // If the user long-pressed on the background, we will share the text (if any)
- // or the first attachment.
- if (mSelectedAttachment == null
- && TextUtil.isAllWhitespace(data.getText())) {
- final List<MessagePartData> attachments = data.getAttachments();
- if (attachments.size() > 0) {
- attachmentToShare = attachments.get(0);
- }
- }
-
- final Intent shareIntent = new Intent();
- shareIntent.setAction(Intent.ACTION_SEND);
- if (attachmentToShare == null) {
- shareIntent.putExtra(Intent.EXTRA_TEXT, data.getText());
- shareIntent.setType("text/plain");
- } else {
- shareIntent.putExtra(
- Intent.EXTRA_STREAM, attachmentToShare.getContentUri());
- shareIntent.setType(attachmentToShare.getContentType());
- }
- final CharSequence title = getResources().getText(R.string.action_share);
- startActivity(Intent.createChooser(shareIntent, title));
- }
-
- @Override
- public void onDestroyActionMode(final ActionMode actionMode) {
- selectMessage(null);
- }
- };
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mFastFlingThreshold = getResources().getDimensionPixelOffset(
- R.dimen.conversation_fast_fling_threshold);
- mAdapter = new ConversationMessageAdapter(getActivity(), null, this,
- null,
- // Sets the item click listener on the Recycler item views.
- new View.OnClickListener() {
- @Override
- public void onClick(final View v) {
- final ConversationMessageView messageView = (ConversationMessageView) v;
- handleMessageClick(messageView);
- }
- },
- new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(final View view) {
- selectMessage((ConversationMessageView) view);
- return true;
- }
- }
- );
- }
-
- /**
- * setConversationInfo() may be called before or after onCreate(). When a user initiate a
- * conversation from compose, the ConversationActivity creates this fragment and calls
- * setConversationInfo(), so it happens before onCreate(). However, when the activity is
- * restored from saved instance state, the ConversationFragment is created automatically by
- * the fragment, before ConversationActivity has a chance to call setConversationInfo(). Since
- * the ability to start loading data depends on both methods being called, we need to start
- * loading when onActivityCreated() is called, which is guaranteed to happen after both.
- */
- @Override
- public void onActivityCreated(final Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- // Delay showing the message list until the participant list is loaded.
- mRecyclerView.setVisibility(View.INVISIBLE);
- mBinding.ensureBound();
- mBinding.getData().init(getLoaderManager(), mBinding);
-
- // Build the input manager with all its required dependencies and pass it along to the
- // compose message view.
- final ConversationInputManager inputManager = new ConversationInputManager(
- getActivity(), this, mComposeMessageView, mHost, getFragmentManagerToUse(),
- mBinding, mComposeMessageView.getDraftDataModel(), savedInstanceState);
- mComposeMessageView.setInputManager(inputManager);
- mComposeMessageView.setConversationDataModel(BindingBase.createBindingReference(mBinding));
- mHost.invalidateActionBar();
-
- mDraftMessageDataModel =
- BindingBase.createBindingReference(mComposeMessageView.getDraftDataModel());
- mDraftMessageDataModel.getData().addListener(this);
- }
-
- public void onAttachmentChoosen() {
- // Attachment has been choosen in the AttachmentChooserActivity, so clear local draft
- // and reload draft on resume.
- mClearLocalDraft = true;
- }
-
- private int getScrollToMessagePosition() {
- final Activity activity = getActivity();
- if (activity == null) {
- return -1;
- }
-
- final Intent intent = activity.getIntent();
- if (intent == null) {
- return -1;
- }
-
- return intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, -1);
- }
-
- private void clearScrollToMessagePosition() {
- final Activity activity = getActivity();
- if (activity == null) {
- return;
- }
-
- final Intent intent = activity.getIntent();
- if (intent == null) {
- return;
- }
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, -1);
- }
-
- private final Handler mHandler = new Handler();
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
- mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list);
- final LinearLayoutManager manager = new LinearLayoutManager(getActivity());
- manager.setStackFromEnd(true);
- manager.setReverseLayout(false);
- mRecyclerView.setHasFixedSize(true);
- mRecyclerView.setLayoutManager(manager);
- mRecyclerView.setItemAnimator(new DefaultItemAnimator() {
- private final List<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
- private PopupTransitionAnimation mPopupTransitionAnimation;
-
- @Override
- public boolean animateAdd(final ViewHolder holder) {
- final ConversationMessageView view =
- (ConversationMessageView) holder.itemView;
- final ConversationMessageData data = view.getData();
- endAnimation(holder);
- final long timeSinceSend = System.currentTimeMillis() - data.getReceivedTimeStamp();
- if (data.getReceivedTimeStamp() ==
- InsertNewMessageAction.getLastSentMessageTimestamp() &&
- !data.getIsIncoming() &&
- timeSinceSend < MESSAGE_ANIMATION_MAX_WAIT) {
- final ConversationMessageBubbleView messageBubble =
- (ConversationMessageBubbleView) view
- .findViewById(R.id.message_content);
- final Rect startRect = UiUtils.getMeasuredBoundsOnScreen(mComposeMessageView);
- final View composeBubbleView = mComposeMessageView.findViewById(
- R.id.compose_message_text);
- final Rect composeBubbleRect =
- UiUtils.getMeasuredBoundsOnScreen(composeBubbleView);
- final AttachmentPreview attachmentView =
- (AttachmentPreview) mComposeMessageView.findViewById(
- R.id.attachment_draft_view);
- final Rect attachmentRect = UiUtils.getMeasuredBoundsOnScreen(attachmentView);
- if (attachmentView.getVisibility() == View.VISIBLE) {
- startRect.top = attachmentRect.top;
- } else {
- startRect.top = composeBubbleRect.top;
- }
- startRect.top -= view.getPaddingTop();
- startRect.bottom =
- composeBubbleRect.bottom;
- startRect.left += view.getPaddingRight();
-
- view.setAlpha(0);
- mPopupTransitionAnimation = new PopupTransitionAnimation(startRect, view);
- mPopupTransitionAnimation.setOnStartCallback(new Runnable() {
- @Override
- public void run() {
- final int startWidth = composeBubbleRect.width();
- attachmentView.onMessageAnimationStart();
- messageBubble.kickOffMorphAnimation(startWidth,
- messageBubble.findViewById(R.id.message_text_and_info)
- .getMeasuredWidth());
- }
- });
- mPopupTransitionAnimation.setOnStopCallback(new Runnable() {
- @Override
- public void run() {
- view.setAlpha(1);
- }
- });
- mPopupTransitionAnimation.startAfterLayoutComplete();
- mAddAnimations.add(holder);
- return true;
- } else {
- return super.animateAdd(holder);
- }
- }
-
- @Override
- public void endAnimation(final ViewHolder holder) {
- if (mAddAnimations.remove(holder)) {
- holder.itemView.clearAnimation();
- }
- super.endAnimation(holder);
- }
-
- @Override
- public void endAnimations() {
- for (final ViewHolder holder : mAddAnimations) {
- holder.itemView.clearAnimation();
- }
- mAddAnimations.clear();
- if (mPopupTransitionAnimation != null) {
- mPopupTransitionAnimation.cancel();
- }
- super.endAnimations();
- }
- });
- mRecyclerView.setAdapter(mAdapter);
-
- if (savedInstanceState != null) {
- mListState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY);
- }
-
- mConversationComposeDivider = view.findViewById(R.id.conversation_compose_divider);
- mScrollToDismissThreshold = ViewConfiguration.get(getActivity()).getScaledTouchSlop();
- mRecyclerView.addOnScrollListener(mListScrollListener);
- mFastScroller = ConversationFastScroller.addTo(mRecyclerView,
- UiUtils.isRtlMode() ? ConversationFastScroller.POSITION_LEFT_SIDE :
- ConversationFastScroller.POSITION_RIGHT_SIDE);
-
- mComposeMessageView = (ComposeMessageView)
- view.findViewById(R.id.message_compose_view_container);
- // Bind the compose message view to the DraftMessageData
- mComposeMessageView.bind(DataModel.get().createDraftMessageData(
- mBinding.getData().getConversationId()), this);
-
- return view;
- }
-
- private void scrollToPosition(final int targetPosition, final boolean smoothScroll) {
- if (smoothScroll) {
- final int maxScrollDelta = JUMP_SCROLL_THRESHOLD;
-
- final LinearLayoutManager layoutManager =
- (LinearLayoutManager) mRecyclerView.getLayoutManager();
- final int firstVisibleItemPosition =
- layoutManager.findFirstVisibleItemPosition();
- final int delta = targetPosition - firstVisibleItemPosition;
- final int intermediatePosition;
-
- if (delta > maxScrollDelta) {
- intermediatePosition = Math.max(0, targetPosition - maxScrollDelta);
- } else if (delta < -maxScrollDelta) {
- final int count = layoutManager.getItemCount();
- intermediatePosition = Math.min(count - 1, targetPosition + maxScrollDelta);
- } else {
- intermediatePosition = -1;
- }
- if (intermediatePosition != -1) {
- mRecyclerView.scrollToPosition(intermediatePosition);
- }
- mRecyclerView.smoothScrollToPosition(targetPosition);
- } else {
- mRecyclerView.scrollToPosition(targetPosition);
- }
- }
-
- private int getScrollPositionFromBottom() {
- final LinearLayoutManager layoutManager =
- (LinearLayoutManager) mRecyclerView.getLayoutManager();
- final int lastVisibleItem =
- layoutManager.findLastVisibleItemPosition();
- return Math.max(mAdapter.getItemCount() - 1 - lastVisibleItem, 0);
- }
-
- /**
- * Display a photo using the Photoviewer component.
- */
- @Override
- public void displayPhoto(final Uri photoUri, final Rect imageBounds, final boolean isDraft) {
- displayPhoto(photoUri, imageBounds, isDraft, mConversationId, getActivity());
- }
-
- public static void displayPhoto(final Uri photoUri, final Rect imageBounds,
- final boolean isDraft, final String conversationId, final Activity activity) {
- final Uri imagesUri =
- isDraft ? MessagingContentProvider.buildDraftImagesUri(conversationId)
- : MessagingContentProvider.buildConversationImagesUri(conversationId);
- UIIntents.get().launchFullScreenPhotoViewer(
- activity, photoUri, imageBounds, imagesUri);
- }
-
- private void selectMessage(final ConversationMessageView messageView) {
- selectMessage(messageView, null /* attachment */);
- }
-
- private void selectMessage(final ConversationMessageView messageView,
- final MessagePartData attachment) {
- mSelectedMessage = messageView;
- if (mSelectedMessage == null) {
- mAdapter.setSelectedMessage(null);
- mHost.dismissActionMode();
- mSelectedAttachment = null;
- return;
- }
- mSelectedAttachment = attachment;
- mAdapter.setSelectedMessage(messageView.getData().getMessageId());
- mHost.startActionMode(mMessageActionModeCallback);
- }
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mListState != null) {
- outState.putParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY, mListState);
- }
- mComposeMessageView.saveInputState(outState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (mIncomingDraft == null) {
- mComposeMessageView.requestDraftMessage(mClearLocalDraft);
- } else {
- mComposeMessageView.setDraftMessage(mIncomingDraft);
- mIncomingDraft = null;
- }
- mClearLocalDraft = false;
-
- // On resume, check if there's a pending request for resuming message compose. This
- // may happen when the user commits the contact selection for a group conversation and
- // goes from compose back to the conversation fragment.
- if (mHost.shouldResumeComposeMessage()) {
- mComposeMessageView.resumeComposeMessage();
- }
-
- setConversationFocus();
-
- // On resume, invalidate all message views to show the updated timestamp.
- mAdapter.notifyDataSetChanged();
-
- LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
- mConversationSelfIdChangeReceiver,
- new IntentFilter(UIIntents.CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION));
- }
-
- void setConversationFocus() {
- if (mHost.isActiveAndFocused()) {
- mBinding.getData().setFocus();
- }
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- if (mHost.getActionMode() != null) {
- return;
- }
-
- inflater.inflate(R.menu.conversation_menu, menu);
-
- final ConversationData data = mBinding.getData();
-
- // Disable the "people & options" item if we haven't loaded participants yet.
- menu.findItem(R.id.action_people_and_options).setEnabled(data.getParticipantsLoaded());
-
- // See if we can show add contact action.
- final ParticipantData participant = data.getOtherParticipant();
- final boolean addContactActionVisible = (participant != null
- && TextUtils.isEmpty(participant.getLookupKey()));
- menu.findItem(R.id.action_add_contact).setVisible(addContactActionVisible);
-
- // See if we should show archive or unarchive.
- final boolean isArchived = data.getIsArchived();
- menu.findItem(R.id.action_archive).setVisible(!isArchived);
- menu.findItem(R.id.action_unarchive).setVisible(isArchived);
-
- // Conditionally enable the phone call button.
- final boolean supportCallAction = (PhoneUtils.getDefault().isVoiceCapable() &&
- data.getParticipantPhoneNumber() != null);
- menu.findItem(R.id.action_call).setVisible(supportCallAction);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_people_and_options:
- Assert.isTrue(mBinding.getData().getParticipantsLoaded());
- UIIntents.get().launchPeopleAndOptionsActivity(getActivity(), mConversationId);
- return true;
-
- case R.id.action_call:
- final String phoneNumber = mBinding.getData().getParticipantPhoneNumber();
- Assert.notNull(phoneNumber);
- final View targetView = getActivity().findViewById(R.id.action_call);
- Point centerPoint;
- if (targetView != null) {
- final int screenLocation[] = new int[2];
- targetView.getLocationOnScreen(screenLocation);
- final int centerX = screenLocation[0] + targetView.getWidth() / 2;
- final int centerY = screenLocation[1] + targetView.getHeight() / 2;
- centerPoint = new Point(centerX, centerY);
- } else {
- // In the overflow menu, just use the center of the screen.
- final Display display = getActivity().getWindowManager().getDefaultDisplay();
- centerPoint = new Point(display.getWidth() / 2, display.getHeight() / 2);
- }
- UIIntents.get().launchPhoneCallActivity(getActivity(), phoneNumber, centerPoint);
- return true;
-
- case R.id.action_archive:
- mBinding.getData().archiveConversation(mBinding);
- closeConversation(mConversationId);
- return true;
-
- case R.id.action_unarchive:
- mBinding.getData().unarchiveConversation(mBinding);
- return true;
-
- case R.id.action_settings:
- return true;
-
- case R.id.action_add_contact:
- final ParticipantData participant = mBinding.getData().getOtherParticipant();
- Assert.notNull(participant);
- final String destination = participant.getNormalizedDestination();
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(participant);
- (new AddContactsConfirmationDialog(getActivity(), avatarUri, destination)).show();
- return true;
-
- case R.id.action_delete:
- if (isReadyForAction()) {
- new AlertDialog.Builder(getActivity())
- .setTitle(getResources().getQuantityString(
- R.plurals.delete_conversations_confirmation_dialog_title, 1))
- .setPositiveButton(R.string.delete_conversation_confirmation_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- deleteConversation();
- }
- })
- .setNegativeButton(R.string.delete_conversation_decline_button, null)
- .show();
- } else {
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- }
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- /**
- * {@inheritDoc} from ConversationDataListener
- */
- @Override
- public void onConversationMessagesCursorUpdated(final ConversationData data,
- final Cursor cursor, final ConversationMessageData newestMessage,
- final boolean isSync) {
- mBinding.ensureBound(data);
-
- // This needs to be determined before swapping cursor, which may change the scroll state.
- final boolean scrolledToBottom = isScrolledToBottom();
- final int positionFromBottom = getScrollPositionFromBottom();
-
- // If participants not loaded, assume 1:1 since that's the 99% case
- final boolean oneOnOne =
- !data.getParticipantsLoaded() || data.getOtherParticipant() != null;
- mAdapter.setOneOnOne(oneOnOne, false /* invalidate */);
-
- // Ensure that the action bar is updated with the current data.
- invalidateOptionsMenu();
- final Cursor oldCursor = mAdapter.swapCursor(cursor);
-
- if (cursor != null && oldCursor == null) {
- if (mListState != null) {
- mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
- // RecyclerView restores scroll states without triggering scroll change events, so
- // we need to manually ensure that they are correctly handled.
- mListScrollListener.onScrolled(mRecyclerView, 0, 0);
- }
- }
-
- if (isSync) {
- // This is a message sync. Syncing messages changes cursor item count, which would
- // implicitly change RV's scroll position. We'd like the RV to keep scrolled to the same
- // relative position from the bottom (because RV is stacked from bottom), so that it
- // stays relatively put as we sync.
- final int position = Math.max(mAdapter.getItemCount() - 1 - positionFromBottom, 0);
- scrollToPosition(position, false /* smoothScroll */);
- } else if (newestMessage != null) {
- // Show a snack bar notification if we are not scrolled to the bottom and the new
- // message is an incoming message.
- if (!scrolledToBottom && newestMessage.getIsIncoming()) {
- // If the conversation activity is started but not resumed (if another dialog
- // activity was in the foregrond), we will show a system notification instead of
- // the snack bar.
- if (mBinding.getData().isFocused()) {
- UiUtils.showSnackBarWithCustomAction(getActivity(),
- getView().getRootView(),
- getString(R.string.in_conversation_notify_new_message_text),
- SnackBar.Action.createCustomAction(new Runnable() {
- @Override
- public void run() {
- scrollToBottom(true /* smoothScroll */);
- mComposeMessageView.hideAllComposeInputs(false /* animate */);
- }
- },
- getString(R.string.in_conversation_notify_new_message_action)),
- null /* interactions */,
- SnackBar.Placement.above(mComposeMessageView));
- }
- } else {
- // We are either already scrolled to the bottom or this is an outgoing message,
- // scroll to the bottom to reveal it.
- // Don't smooth scroll if we were already at the bottom; instead, we scroll
- // immediately so RecyclerView's view animation will take place.
- scrollToBottom(!scrolledToBottom);
- }
- }
-
- if (cursor != null) {
- mHost.onConversationMessagesUpdated(cursor.getCount());
-
- // Are we coming from a widget click where we're told to scroll to a particular item?
- final int scrollToPos = getScrollToMessagePosition();
- if (scrollToPos >= 0) {
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, "onConversationMessagesCursorUpdated " +
- " scrollToPos: " + scrollToPos +
- " cursorCount: " + cursor.getCount());
- }
- scrollToPosition(scrollToPos, true /*smoothScroll*/);
- clearScrollToMessagePosition();
- }
- }
-
- mHost.invalidateActionBar();
- }
-
- /**
- * {@inheritDoc} from ConversationDataListener
- */
- @Override
- public void onConversationMetadataUpdated(final ConversationData conversationData) {
- mBinding.ensureBound(conversationData);
-
- if (mSelectedMessage != null && mSelectedAttachment != null) {
- // We may have just sent a message and the temp attachment we selected is now gone.
- // and it was replaced with some new attachment. Since we don't know which one it
- // is we shouldn't reselect it (unless there is just one) In the multi-attachment
- // case we would just deselect the message and allow the user to reselect, otherwise we
- // may act on old temp data and may crash.
- final List<MessagePartData> currentAttachments = mSelectedMessage.getData().getAttachments();
- if (currentAttachments.size() == 1) {
- mSelectedAttachment = currentAttachments.get(0);
- } else if (!currentAttachments.contains(mSelectedAttachment)) {
- selectMessage(null);
- }
- }
- // Ensure that the action bar is updated with the current data.
- invalidateOptionsMenu();
- mHost.onConversationMetadataUpdated();
- mAdapter.notifyDataSetChanged();
- }
-
- public void setConversationInfo(final Context context, final String conversationId,
- final MessageData draftData) {
- // TODO: Eventually I would like the Factory to implement
- // Factory.get().bindConversationData(mBinding, getActivity(), this, conversationId));
- if (!mBinding.isBound()) {
- mConversationId = conversationId;
- mIncomingDraft = draftData;
- mBinding.bind(DataModel.get().createConversationData(context, this, conversationId));
- } else {
- Assert.isTrue(TextUtils.equals(mBinding.getData().getConversationId(), conversationId));
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- // Unbind all the views that we bound to data
- if (mComposeMessageView != null) {
- mComposeMessageView.unbind();
- }
-
- // And unbind this fragment from its data
- mBinding.unbind();
- mConversationId = null;
- }
-
- void suppressWriteDraft() {
- mSuppressWriteDraft = true;
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (mComposeMessageView != null && !mSuppressWriteDraft) {
- mComposeMessageView.writeDraftMessage();
- }
- mSuppressWriteDraft = false;
- mBinding.getData().unsetFocus();
- mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
-
- LocalBroadcastManager.getInstance(getActivity())
- .unregisterReceiver(mConversationSelfIdChangeReceiver);
- }
-
- @Override
- public void onConfigurationChanged(final Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mRecyclerView.getItemAnimator().endAnimations();
- }
-
- // TODO: Remove isBound and replace it with ensureBound after b/15704674.
- public boolean isBound() {
- return mBinding.isBound();
- }
-
- private FragmentManager getFragmentManagerToUse() {
- return OsUtil.isAtLeastJB_MR1() ? getChildFragmentManager() : getFragmentManager();
- }
-
- public MediaPicker getMediaPicker() {
- return (MediaPicker) getFragmentManagerToUse().findFragmentByTag(
- MediaPicker.FRAGMENT_TAG);
- }
-
- @Override
- public void sendMessage(final MessageData message) {
- if (isReadyForAction()) {
- if (ensureKnownRecipients()) {
- // Merge the caption text from attachments into the text body of the messages
- message.consolidateText();
-
- mBinding.getData().sendMessage(mBinding, message);
- mComposeMessageView.resetMediaPickerState();
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: conv participants not loaded");
- }
- } else {
- warnOfMissingActionConditions(true /*sending*/,
- new Runnable() {
- @Override
- public void run() {
- sendMessage(message);
- }
- });
- }
- }
-
- public void setHost(final ConversationFragmentHost host) {
- mHost = host;
- }
-
- public String getConversationName() {
- return mBinding.getData().getConversationName();
- }
-
- @Override
- public void onComposeEditTextFocused() {
- mHost.onStartComposeMessage();
- }
-
- @Override
- public void onAttachmentsCleared() {
- // When attachments are removed, reset transient media picker state such as image selection.
- mComposeMessageView.resetMediaPickerState();
- }
-
- /**
- * Called to check if all conditions are nominal and a "go" for some action, such as deleting
- * a message, that requires this app to be the default app. This is also a precondition
- * required for sending a draft.
- * @return true if all conditions are nominal and we're ready to send a message
- */
- @Override
- public boolean isReadyForAction() {
- return UiUtils.isReadyForAction();
- }
-
- /**
- * When there's some condition that prevents an operation, such as sending a message,
- * call warnOfMissingActionConditions to put up a snackbar and allow the user to repair
- * that condition.
- * @param sending - true if we're called during a sending operation
- * @param commandToRunAfterActionConditionResolved - a runnable to run after the user responds
- * positively to the condition prompt and resolves the condition. If null,
- * the user will be shown a toast to tap the send button again.
- */
- @Override
- public void warnOfMissingActionConditions(final boolean sending,
- final Runnable commandToRunAfterActionConditionResolved) {
- if (mChangeDefaultSmsAppHelper == null) {
- mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
- }
- mChangeDefaultSmsAppHelper.warnOfMissingActionConditions(sending,
- commandToRunAfterActionConditionResolved, mComposeMessageView,
- getView().getRootView(),
- getActivity(), this);
- }
-
- private boolean ensureKnownRecipients() {
- final ConversationData conversationData = mBinding.getData();
-
- if (!conversationData.getParticipantsLoaded()) {
- // We can't tell yet whether or not we have an unknown recipient
- return false;
- }
-
- final ConversationParticipantsData participants = conversationData.getParticipants();
- for (final ParticipantData participant : participants) {
-
-
- if (participant.isUnknownSender()) {
- UiUtils.showToast(R.string.unknown_sender);
- return false;
- }
- }
-
- return true;
- }
-
- public void retryDownload(final String messageId) {
- if (isReadyForAction()) {
- mBinding.getData().downloadMessage(mBinding, messageId);
- } else {
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- }
- }
-
- public void retrySend(final String messageId) {
- if (isReadyForAction()) {
- if (ensureKnownRecipients()) {
- mBinding.getData().resendMessage(mBinding, messageId);
- }
- } else {
- warnOfMissingActionConditions(true /*sending*/,
- new Runnable() {
- @Override
- public void run() {
- retrySend(messageId);
- }
-
- });
- }
- }
-
- void deleteMessage(final String messageId) {
- if (isReadyForAction()) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.delete_message_confirmation_dialog_title)
- .setMessage(R.string.delete_message_confirmation_dialog_text)
- .setPositiveButton(R.string.delete_message_confirmation_button,
- new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- mBinding.getData().deleteMessage(mBinding, messageId);
- }
- })
- .setNegativeButton(android.R.string.cancel, null);
- if (OsUtil.isAtLeastJB_MR1()) {
- builder.setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss(final DialogInterface dialog) {
- mHost.dismissActionMode();
- }
- });
- } else {
- builder.setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(final DialogInterface dialog) {
- mHost.dismissActionMode();
- }
- });
- }
- builder.create().show();
- } else {
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- mHost.dismissActionMode();
- }
- }
-
- public void deleteConversation() {
- if (isReadyForAction()) {
- final Context context = getActivity();
- mBinding.getData().deleteConversation(mBinding);
- closeConversation(mConversationId);
- } else {
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- }
- }
-
- @Override
- public void closeConversation(final String conversationId) {
- if (TextUtils.equals(conversationId, mConversationId)) {
- mHost.onFinishCurrentConversation();
- // TODO: Explicitly transition to ConversationList (or just go back)?
- }
- }
-
- @Override
- public void onConversationParticipantDataLoaded(final ConversationData data) {
- mBinding.ensureBound(data);
- if (mBinding.getData().getParticipantsLoaded()) {
- final boolean oneOnOne = mBinding.getData().getOtherParticipant() != null;
- mAdapter.setOneOnOne(oneOnOne, true /* invalidate */);
-
- // refresh the options menu which will enable the "people & options" item.
- invalidateOptionsMenu();
-
- mHost.invalidateActionBar();
-
- mRecyclerView.setVisibility(View.VISIBLE);
- mHost.onConversationParticipantDataLoaded
- (mBinding.getData().getNumberOfParticipantsExcludingSelf());
- }
- }
-
- @Override
- public void onSubscriptionListDataLoaded(final ConversationData data) {
- mBinding.ensureBound(data);
- mAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void promptForSelfPhoneNumber() {
- if (mComposeMessageView != null) {
- // Avoid bug in system which puts soft keyboard over dialog after orientation change
- ImeUtil.hideSoftInput(getActivity(), mComposeMessageView);
- }
-
- final FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
- final EnterSelfPhoneNumberDialog dialog = EnterSelfPhoneNumberDialog
- .newInstance(getConversationSelfSubId());
- dialog.setTargetFragment(this, 0/*requestCode*/);
- dialog.show(ft, null/*tag*/);
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- if (mChangeDefaultSmsAppHelper == null) {
- mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
- }
- mChangeDefaultSmsAppHelper.handleChangeDefaultSmsResult(requestCode, resultCode, null);
- }
-
- public boolean hasMessages() {
- return mAdapter != null && mAdapter.getItemCount() > 0;
- }
-
- public boolean onBackPressed() {
- if (mComposeMessageView.onBackPressed()) {
- return true;
- }
- return false;
- }
-
- public boolean onNavigationUpPressed() {
- return mComposeMessageView.onNavigationUpPressed();
- }
-
- @Override
- public boolean onAttachmentClick(final ConversationMessageView messageView,
- final MessagePartData attachment, final Rect imageBounds, final boolean longPress) {
- if (longPress) {
- selectMessage(messageView, attachment);
- return true;
- } else if (messageView.getData().getOneClickResendMessage()) {
- handleMessageClick(messageView);
- return true;
- }
-
- if (attachment.isImage()) {
- displayPhoto(attachment.getContentUri(), imageBounds, false /* isDraft */);
- }
-
- if (attachment.isVCard()) {
- UIIntents.get().launchVCardDetailActivity(getActivity(), attachment.getContentUri());
- }
-
- return false;
- }
-
- private void handleMessageClick(final ConversationMessageView messageView) {
- if (messageView != mSelectedMessage) {
- final ConversationMessageData data = messageView.getData();
- final boolean isReadyToSend = isReadyForAction();
- if (data.getOneClickResendMessage()) {
- // Directly resend the message on tap if it's failed
- retrySend(data.getMessageId());
- selectMessage(null);
- } else if (data.getShowResendMessage() && isReadyToSend) {
- // Select the message to show the resend/download/delete options
- selectMessage(messageView);
- } else if (data.getShowDownloadMessage() && isReadyToSend) {
- // Directly download the message on tap
- retryDownload(data.getMessageId());
- } else {
- // Let the toast from warnOfMissingActionConditions show and skip
- // selecting
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- selectMessage(null);
- }
- } else {
- selectMessage(null);
- }
- }
-
- private static class AttachmentToSave {
- public final Uri uri;
- public final String contentType;
- public Uri persistedUri;
-
- AttachmentToSave(final Uri uri, final String contentType) {
- this.uri = uri;
- this.contentType = contentType;
- }
- }
-
- public static class SaveAttachmentTask extends SafeAsyncTask<Void, Void, Void> {
- private final Context mContext;
- private final List<AttachmentToSave> mAttachmentsToSave = new ArrayList<>();
-
- public SaveAttachmentTask(final Context context, final Uri contentUri,
- final String contentType) {
- mContext = context;
- addAttachmentToSave(contentUri, contentType);
- }
-
- public SaveAttachmentTask(final Context context) {
- mContext = context;
- }
-
- public void addAttachmentToSave(final Uri contentUri, final String contentType) {
- mAttachmentsToSave.add(new AttachmentToSave(contentUri, contentType));
- }
-
- public int getAttachmentCount() {
- return mAttachmentsToSave.size();
- }
-
- @Override
- protected Void doInBackgroundTimed(final Void... arg) {
- final File appDir = new File(Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES),
- mContext.getResources().getString(R.string.app_name));
- final File downloadDir = Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_DOWNLOADS);
- for (final AttachmentToSave attachment : mAttachmentsToSave) {
- final boolean isImageOrVideo = ContentType.isImageType(attachment.contentType)
- || ContentType.isVideoType(attachment.contentType);
- attachment.persistedUri = UriUtil.persistContent(attachment.uri,
- isImageOrVideo ? appDir : downloadDir, attachment.contentType);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(final Void result) {
- int failCount = 0;
- int imageCount = 0;
- int videoCount = 0;
- int otherCount = 0;
- for (final AttachmentToSave attachment : mAttachmentsToSave) {
- if (attachment.persistedUri == null) {
- failCount++;
- continue;
- }
-
- // Inform MediaScanner about the new file
- final Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- scanFileIntent.setData(attachment.persistedUri);
- mContext.sendBroadcast(scanFileIntent);
-
- if (ContentType.isImageType(attachment.contentType)) {
- imageCount++;
- } else if (ContentType.isVideoType(attachment.contentType)) {
- videoCount++;
- } else {
- otherCount++;
- // Inform DownloadManager of the file so it will show in the "downloads" app
- final DownloadManager downloadManager =
- (DownloadManager) mContext.getSystemService(
- Context.DOWNLOAD_SERVICE);
- final String filePath = attachment.persistedUri.getPath();
- final File file = new File(filePath);
-
- if (file.exists()) {
- downloadManager.addCompletedDownload(
- file.getName() /* title */,
- mContext.getString(
- R.string.attachment_file_description) /* description */,
- true /* isMediaScannerScannable */,
- attachment.contentType,
- file.getAbsolutePath(),
- file.length(),
- false /* showNotification */);
- }
- }
- }
-
- String message;
- if (failCount > 0) {
- message = mContext.getResources().getQuantityString(
- R.plurals.attachment_save_error, failCount, failCount);
- } else {
- int messageId = R.plurals.attachments_saved;
- if (otherCount > 0) {
- if (imageCount + videoCount == 0) {
- messageId = R.plurals.attachments_saved_to_downloads;
- }
- } else {
- if (videoCount == 0) {
- messageId = R.plurals.photos_saved_to_album;
- } else if (imageCount == 0) {
- messageId = R.plurals.videos_saved_to_album;
- } else {
- messageId = R.plurals.attachments_saved_to_album;
- }
- }
- final String appName = mContext.getResources().getString(R.string.app_name);
- final int count = imageCount + videoCount + otherCount;
- message = mContext.getResources().getQuantityString(
- messageId, count, count, appName);
- }
- UiUtils.showToastAtBottom(message);
- }
- }
-
- private void invalidateOptionsMenu() {
- final Activity activity = getActivity();
- // TODO: Add the supportInvalidateOptionsMenu call to the host activity.
- if (activity == null || !(activity instanceof BugleActionBarActivity)) {
- return;
- }
- ((BugleActionBarActivity) activity).supportInvalidateOptionsMenu();
- }
-
- @Override
- public void setOptionsMenuVisibility(final boolean visible) {
- setHasOptionsMenu(visible);
- }
-
- @Override
- public int getConversationSelfSubId() {
- final String selfParticipantId = mComposeMessageView.getConversationSelfId();
- final ParticipantData self = mBinding.getData().getSelfParticipantById(selfParticipantId);
- // If the self id or the self participant data hasn't been loaded yet, fallback to
- // the default setting.
- return self == null ? ParticipantData.DEFAULT_SELF_SUB_ID : self.getSubId();
- }
-
- @Override
- public void invalidateActionBar() {
- mHost.invalidateActionBar();
- }
-
- @Override
- public void dismissActionMode() {
- mHost.dismissActionMode();
- }
-
- @Override
- public void selectSim(final SubscriptionListEntry subscriptionData) {
- mComposeMessageView.selectSim(subscriptionData);
- mHost.onStartComposeMessage();
- }
-
- @Override
- public void onStartComposeMessage() {
- mHost.onStartComposeMessage();
- }
-
- @Override
- public SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
- final String selfParticipantId, final boolean excludeDefault) {
- // TODO: ConversationMessageView is the only one using this. We should probably
- // inject this into the view during binding in the ConversationMessageAdapter.
- return mBinding.getData().getSubscriptionEntryForSelfParticipant(selfParticipantId,
- excludeDefault);
- }
-
- @Override
- public SimSelectorView getSimSelectorView() {
- return (SimSelectorView) getView().findViewById(R.id.sim_selector);
- }
-
- @Override
- public MediaPicker createMediaPicker() {
- return new MediaPicker(getActivity());
- }
-
- @Override
- public void notifyOfAttachmentLoadFailed() {
- UiUtils.showToastAtBottom(R.string.attachment_load_failed_dialog_message);
- }
-
- @Override
- public void warnOfExceedingMessageLimit(final boolean sending, final boolean tooManyVideos) {
- warnOfExceedingMessageLimit(sending, mComposeMessageView, mConversationId,
- getActivity(), tooManyVideos);
- }
-
- public static void warnOfExceedingMessageLimit(final boolean sending,
- final ComposeMessageView composeMessageView, final String conversationId,
- final Activity activity, final boolean tooManyVideos) {
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(activity)
- .setTitle(R.string.mms_attachment_limit_reached);
-
- if (sending) {
- if (tooManyVideos) {
- builder.setMessage(R.string.video_attachment_limit_exceeded_when_sending);
- } else {
- builder.setMessage(R.string.attachment_limit_reached_dialog_message_when_sending)
- .setNegativeButton(R.string.attachment_limit_reached_send_anyway,
- new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int which) {
- composeMessageView.sendMessageIgnoreMessageSizeLimit();
- }
- });
- }
- builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- showAttachmentChooser(conversationId, activity);
- }});
- } else {
- builder.setMessage(R.string.attachment_limit_reached_dialog_message_when_composing)
- .setPositiveButton(android.R.string.ok, null);
- }
- builder.show();
- }
-
- @Override
- public void showAttachmentChooser() {
- showAttachmentChooser(mConversationId, getActivity());
- }
-
- public static void showAttachmentChooser(final String conversationId,
- final Activity activity) {
- UIIntents.get().launchAttachmentChooserActivity(activity,
- conversationId, REQUEST_CHOOSE_ATTACHMENTS);
- }
-
- private void updateActionAndStatusBarColor(final ActionBar actionBar) {
- final int themeColor = ConversationDrawables.get().getConversationThemeColor();
- actionBar.setBackgroundDrawable(new ColorDrawable(themeColor));
- UiUtils.setStatusBarColor(getActivity(), themeColor);
- }
-
- public void updateActionBar(final ActionBar actionBar) {
- if (mComposeMessageView == null || !mComposeMessageView.updateActionBar(actionBar)) {
- updateActionAndStatusBarColor(actionBar);
- // We update this regardless of whether or not the action bar is showing so that we
- // don't get a race when it reappears.
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
- actionBar.setDisplayHomeAsUpEnabled(true);
- // Reset the back arrow to its default
- actionBar.setHomeAsUpIndicator(0);
- View customView = actionBar.getCustomView();
- if (customView == null || customView.getId() != R.id.conversation_title_container) {
- final LayoutInflater inflator = (LayoutInflater)
- getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- customView = inflator.inflate(R.layout.action_bar_conversation_name, null);
- customView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View v) {
- onBackPressed();
- }
- });
- actionBar.setCustomView(customView);
- }
-
- final TextView conversationNameView =
- (TextView) customView.findViewById(R.id.conversation_title);
- final String conversationName = getConversationName();
- if (!TextUtils.isEmpty(conversationName)) {
- // RTL : To format conversation title if it happens to be phone numbers.
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String formattedName = bidiFormatter.unicodeWrap(
- UiUtils.commaEllipsize(
- conversationName,
- conversationNameView.getPaint(),
- conversationNameView.getWidth(),
- getString(R.string.plus_one),
- getString(R.string.plus_n)).toString(),
- TextDirectionHeuristicsCompat.LTR);
- conversationNameView.setText(formattedName);
- // In case phone numbers are mixed in the conversation name, we need to vocalize it.
- final String vocalizedConversationName =
- AccessibilityUtil.getVocalizedPhoneNumber(getResources(), conversationName);
- conversationNameView.setContentDescription(vocalizedConversationName);
- getActivity().setTitle(conversationName);
- } else {
- final String appName = getString(R.string.app_name);
- conversationNameView.setText(appName);
- getActivity().setTitle(appName);
- }
-
- // When conversation is showing and media picker is not showing, then hide the action
- // bar only when we are in landscape mode, with IME open.
- if (mHost.isImeOpen() && UiUtils.isLandscapeMode()) {
- actionBar.hide();
- } else {
- actionBar.show();
- }
- }
- }
-
- @Override
- public boolean shouldShowSubjectEditor() {
- return true;
- }
-
- @Override
- public boolean shouldHideAttachmentsWhenSimSelectorShown() {
- return false;
- }
-
- @Override
- public void showHideSimSelector(final boolean show) {
- // no-op for now
- }
-
- @Override
- public int getSimSelectorItemLayoutId() {
- return R.layout.sim_selector_item_view;
- }
-
- @Override
- public Uri getSelfSendButtonIconUri() {
- return null; // use default button icon uri
- }
-
- @Override
- public int overrideCounterColor() {
- return -1; // don't override the color
- }
-
- @Override
- public void onAttachmentsChanged(final boolean haveAttachments) {
- // no-op for now
- }
-
- @Override
- public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
- mDraftMessageDataModel.ensureBound(data);
- // We're specifically only interested in ATTACHMENTS_CHANGED from the widget. Ignore
- // other changes. When the widget changes an attachment, we need to reload the draft.
- if (changeFlags ==
- (DraftMessageData.WIDGET_CHANGED | DraftMessageData.ATTACHMENTS_CHANGED)) {
- mClearLocalDraft = true; // force a reload of the draft in onResume
- }
- }
-
- @Override
- public void onDraftAttachmentLimitReached(final DraftMessageData data) {
- // no-op for now
- }
-
- @Override
- public void onDraftAttachmentLoadFailed() {
- // no-op for now
- }
-
- @Override
- public int getAttachmentsClearedFlags() {
- return DraftMessageData.ATTACHMENTS_CHANGED;
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationInput.java b/src/com/android/messaging/ui/conversation/ConversationInput.java
deleted file mode 100644
index bf60aa8..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationInput.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-
-/**
- * The base class for a method of user input, e.g. media picker.
- */
-public abstract class ConversationInput {
- /**
- * The host component where all input components are contained. This is typically the
- * conversation fragment but may be mocked in test code.
- */
- public interface ConversationInputBase {
- boolean showHideInternal(final ConversationInput target, final boolean show,
- final boolean animate);
- String getInputStateKey(final ConversationInput input);
- void beginUpdate();
- void handleOnShow(final ConversationInput target);
- void endUpdate();
- }
-
- protected boolean mShowing;
- protected ConversationInputBase mConversationInputBase;
-
- public abstract boolean show(boolean animate);
- public abstract boolean hide(boolean animate);
-
- public ConversationInput(ConversationInputBase baseHost, final boolean isShowing) {
- mConversationInputBase = baseHost;
- mShowing = isShowing;
- }
-
- public boolean onBackPressed() {
- if (mShowing) {
- mConversationInputBase.showHideInternal(this, false /* show */, true /* animate */);
- return true;
- }
- return false;
- }
-
- public boolean onNavigationUpPressed() {
- return false;
- }
-
- /**
- * Toggle the visibility of this view.
- * @param animate
- * @return true if the view is now shown, false if it now hidden
- */
- public boolean toggle(final boolean animate) {
- mConversationInputBase.showHideInternal(this, !mShowing /* show */, true /* animate */);
- return mShowing;
- }
-
- public void saveState(final Bundle savedState) {
- savedState.putBoolean(mConversationInputBase.getInputStateKey(this), mShowing);
- }
-
- public void restoreState(final Bundle savedState) {
- // Things are hidden by default, so only handle show.
- if (savedState.getBoolean(mConversationInputBase.getInputStateKey(this))) {
- mConversationInputBase.showHideInternal(this, true /* show */, false /* animate */);
- }
- }
-
- public boolean updateActionBar(final ActionBar actionBar) {
- return false;
- }
-
- /**
- * Update our visibility flag in response to visibility change, both for actions
- * initiated by this class (through the show/hide methods), and for external changes
- * tracked by event listeners (e.g. ImeStateObserver, MediaPickerListener). As part of
- * handling an input showing, we will hide all other inputs to ensure they are mutually
- * exclusive.
- */
- protected void onVisibilityChanged(final boolean visible) {
- if (mShowing != visible) {
- mConversationInputBase.beginUpdate();
- mShowing = visible;
- if (visible) {
- mConversationInputBase.handleOnShow(this);
- }
- mConversationInputBase.endUpdate();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationInputManager.java b/src/com/android/messaging/ui/conversation/ConversationInputManager.java
deleted file mode 100644
index e10abe7..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationInputManager.java
+++ /dev/null
@@ -1,550 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.app.FragmentManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.widget.EditText;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.ConversationData;
-import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
-import com.android.messaging.datamodel.data.ConversationData.SimpleConversationDataListener;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.ui.mediapicker.MediaPicker;
-import com.android.messaging.ui.mediapicker.MediaPicker.MediaPickerListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.ImeUtil.ImeStateHost;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.Collection;
-
-/**
- * Manages showing/hiding/persisting different mutually exclusive UI components nested in
- * ConversationFragment that take user inputs, i.e. media picker, SIM selector and
- * IME keyboard (the IME keyboard is not owned by Bugle, but we try to model it the same way
- * as the other components).
- */
-public class ConversationInputManager implements ConversationInput.ConversationInputBase {
- /**
- * The host component where all input components are contained. This is typically the
- * conversation fragment but may be mocked in test code.
- */
- public interface ConversationInputHost extends DraftMessageSubscriptionDataProvider {
- void invalidateActionBar();
- void setOptionsMenuVisibility(boolean visible);
- void dismissActionMode();
- void selectSim(SubscriptionListEntry subscriptionData);
- void onStartComposeMessage();
- SimSelectorView getSimSelectorView();
- MediaPicker createMediaPicker();
- void showHideSimSelector(boolean show);
- int getSimSelectorItemLayoutId();
- }
-
- /**
- * The "sink" component where all inputs components will direct the user inputs to. This is
- * typically the ComposeMessageView but may be mocked in test code.
- */
- public interface ConversationInputSink {
- void onMediaItemsSelected(Collection<MessagePartData> items);
- void onMediaItemsUnselected(MessagePartData item);
- void onPendingAttachmentAdded(PendingAttachmentData pendingItem);
- void resumeComposeMessage();
- EditText getComposeEditText();
- void setAccessibility(boolean enabled);
- }
-
- private final ConversationInputHost mHost;
- private final ConversationInputSink mSink;
-
- /** Dependencies injected from the host during construction */
- private final FragmentManager mFragmentManager;
- private final Context mContext;
- private final ImeStateHost mImeStateHost;
- private final ImmutableBindingRef<ConversationData> mConversationDataModel;
- private final ImmutableBindingRef<DraftMessageData> mDraftDataModel;
-
- private final ConversationInput[] mInputs;
- private final ConversationMediaPicker mMediaInput;
- private final ConversationSimSelector mSimInput;
- private final ConversationImeKeyboard mImeInput;
- private int mUpdateCount;
-
- private final ImeUtil.ImeStateObserver mImeStateObserver = new ImeUtil.ImeStateObserver() {
- @Override
- public void onImeStateChanged(final boolean imeOpen) {
- mImeInput.onVisibilityChanged(imeOpen);
- }
- };
-
- private final ConversationDataListener mDataListener = new SimpleConversationDataListener() {
- @Override
- public void onConversationParticipantDataLoaded(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- }
-
- @Override
- public void onSubscriptionListDataLoaded(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- mSimInput.onSubscriptionListDataLoaded(data.getSubscriptionListData());
- }
- };
-
- public ConversationInputManager(
- final Context context,
- final ConversationInputHost host,
- final ConversationInputSink sink,
- final ImeStateHost imeStateHost,
- final FragmentManager fm,
- final BindingBase<ConversationData> conversationDataModel,
- final BindingBase<DraftMessageData> draftDataModel,
- final Bundle savedState) {
- mHost = host;
- mSink = sink;
- mFragmentManager = fm;
- mContext = context;
- mImeStateHost = imeStateHost;
- mConversationDataModel = BindingBase.createBindingReference(conversationDataModel);
- mDraftDataModel = BindingBase.createBindingReference(draftDataModel);
-
- // Register listeners on dependencies.
- mImeStateHost.registerImeStateObserver(mImeStateObserver);
- mConversationDataModel.getData().addConversationDataListener(mDataListener);
-
- // Initialize the inputs
- mMediaInput = new ConversationMediaPicker(this);
- mSimInput = new SimSelector(this);
- mImeInput = new ConversationImeKeyboard(this, mImeStateHost.isImeOpen());
- mInputs = new ConversationInput[] { mMediaInput, mSimInput, mImeInput };
-
- if (savedState != null) {
- for (int i = 0; i < mInputs.length; i++) {
- mInputs[i].restoreState(savedState);
- }
- }
- updateHostOptionsMenu();
- }
-
- public void onDetach() {
- mImeStateHost.unregisterImeStateObserver(mImeStateObserver);
- // Don't need to explicitly unregister for data model events. It will unregister all
- // listeners automagically on unbind.
- }
-
- public void onSaveInputState(final Bundle savedState) {
- for (int i = 0; i < mInputs.length; i++) {
- mInputs[i].saveState(savedState);
- }
- }
-
- @Override
- public String getInputStateKey(final ConversationInput input) {
- return input.getClass().getCanonicalName() + "_savedstate_";
- }
-
- public boolean onBackPressed() {
- for (int i = 0; i < mInputs.length; i++) {
- if (mInputs[i].onBackPressed()) {
- return true;
- }
- }
- return false;
- }
-
- public boolean onNavigationUpPressed() {
- for (int i = 0; i < mInputs.length; i++) {
- if (mInputs[i].onNavigationUpPressed()) {
- return true;
- }
- }
- return false;
- }
-
- public void resetMediaPickerState() {
- mMediaInput.resetViewHolderState();
- }
-
- public void showHideMediaPicker(final boolean show, final boolean animate) {
- showHideInternal(mMediaInput, show, animate);
- }
-
- /**
- * Show or hide the sim selector
- * @param show visibility
- * @param animate whether to animate the change in visibility
- * @return true if the state of the visibility was changed
- */
- public boolean showHideSimSelector(final boolean show, final boolean animate) {
- return showHideInternal(mSimInput, show, animate);
- }
-
- public void showHideImeKeyboard(final boolean show, final boolean animate) {
- showHideInternal(mImeInput, show, animate);
- }
-
- public void hideAllInputs(final boolean animate) {
- beginUpdate();
- for (int i = 0; i < mInputs.length; i++) {
- showHideInternal(mInputs[i], false, animate);
- }
- endUpdate();
- }
-
- /**
- * Toggle the visibility of the sim selector.
- * @param animate
- * @param subEntry
- * @return true if the view is now shown, false if it now hidden
- */
- public boolean toggleSimSelector(final boolean animate, final SubscriptionListEntry subEntry) {
- mSimInput.setSelected(subEntry);
- return mSimInput.toggle(animate);
- }
-
- public boolean updateActionBar(final ActionBar actionBar) {
- for (int i = 0; i < mInputs.length; i++) {
- if (mInputs[i].mShowing) {
- return mInputs[i].updateActionBar(actionBar);
- }
- }
- return false;
- }
-
- @VisibleForTesting
- boolean isMediaPickerVisible() {
- return mMediaInput.mShowing;
- }
-
- @VisibleForTesting
- boolean isSimSelectorVisible() {
- return mSimInput.mShowing;
- }
-
- @VisibleForTesting
- boolean isImeKeyboardVisible() {
- return mImeInput.mShowing;
- }
-
- @VisibleForTesting
- void testNotifyImeStateChanged(final boolean imeOpen) {
- mImeStateObserver.onImeStateChanged(imeOpen);
- }
-
- /**
- * returns true if the state of the visibility was actually changed
- */
- @Override
- public boolean showHideInternal(final ConversationInput target, final boolean show,
- final boolean animate) {
- if (!mConversationDataModel.isBound()) {
- return false;
- }
-
- if (target.mShowing == show) {
- return false;
- }
- beginUpdate();
- boolean success;
- if (!show) {
- success = target.hide(animate);
- } else {
- success = target.show(animate);
- }
-
- if (success) {
- target.onVisibilityChanged(show);
- }
- endUpdate();
- return true;
- }
-
- @Override
- public void handleOnShow(final ConversationInput target) {
- if (!mConversationDataModel.isBound()) {
- return;
- }
- beginUpdate();
-
- // All inputs are mutually exclusive. Showing one will hide everything else.
- // The one exception, is that the keyboard and location media chooser can be open at the
- // time to enable searching within that chooser
- for (int i = 0; i < mInputs.length; i++) {
- final ConversationInput currInput = mInputs[i];
- if (currInput != target) {
- // TODO : If there's more exceptions we will want to make this more
- // generic
- if (currInput instanceof ConversationMediaPicker &&
- target instanceof ConversationImeKeyboard &&
- mMediaInput.getExistingOrCreateMediaPicker() != null &&
- mMediaInput.getExistingOrCreateMediaPicker().canShowIme()) {
- // Allow the keyboard and location mediaPicker to be open at the same time,
- // but ensure the media picker is full screen to allow enough room
- mMediaInput.getExistingOrCreateMediaPicker().setFullScreen(true);
- continue;
- }
- showHideInternal(currInput, false /* show */, false /* animate */);
- }
- }
- // Always dismiss action mode on show.
- mHost.dismissActionMode();
- // Invoking any non-keyboard input UI is treated as starting message compose.
- if (target != mImeInput) {
- mHost.onStartComposeMessage();
- }
- endUpdate();
- }
-
- @Override
- public void beginUpdate() {
- mUpdateCount++;
- }
-
- @Override
- public void endUpdate() {
- Assert.isTrue(mUpdateCount > 0);
- if (--mUpdateCount == 0) {
- // Always try to update the host action bar after every update cycle.
- mHost.invalidateActionBar();
- }
- }
-
- private void updateHostOptionsMenu() {
- mHost.setOptionsMenuVisibility(!mMediaInput.isOpen());
- }
-
- /**
- * Manages showing/hiding the media picker in conversation.
- */
- private class ConversationMediaPicker extends ConversationInput {
- public ConversationMediaPicker(ConversationInputBase baseHost) {
- super(baseHost, false);
- }
-
- private MediaPicker mMediaPicker;
-
- @Override
- public boolean show(boolean animate) {
- if (mMediaPicker == null) {
- mMediaPicker = getExistingOrCreateMediaPicker();
- setConversationThemeColor(ConversationDrawables.get().getConversationThemeColor());
- mMediaPicker.setSubscriptionDataProvider(mHost);
- mMediaPicker.setDraftMessageDataModel(mDraftDataModel);
- mMediaPicker.setListener(new MediaPickerListener() {
- @Override
- public void onOpened() {
- handleStateChange();
- }
-
- @Override
- public void onFullScreenChanged(boolean fullScreen) {
- // When we're full screen, we want to disable accessibility on the
- // ComposeMessageView controls (attach button, message input, sim chooser)
- // that are hiding underneath the action bar.
- mSink.setAccessibility(!fullScreen /*enabled*/);
- handleStateChange();
- }
-
- @Override
- public void onDismissed() {
- // Re-enable accessibility on all controls now that the media picker is
- // going away.
- mSink.setAccessibility(true /*enabled*/);
- handleStateChange();
- }
-
- private void handleStateChange() {
- onVisibilityChanged(isOpen());
- mHost.invalidateActionBar();
- updateHostOptionsMenu();
- }
-
- @Override
- public void onItemsSelected(final Collection<MessagePartData> items,
- final boolean resumeCompose) {
- mSink.onMediaItemsSelected(items);
- mHost.invalidateActionBar();
- if (resumeCompose) {
- mSink.resumeComposeMessage();
- }
- }
-
- @Override
- public void onItemUnselected(final MessagePartData item) {
- mSink.onMediaItemsUnselected(item);
- mHost.invalidateActionBar();
- }
-
- @Override
- public void onConfirmItemSelection() {
- mSink.resumeComposeMessage();
- }
-
- @Override
- public void onPendingItemAdded(final PendingAttachmentData pendingItem) {
- mSink.onPendingAttachmentAdded(pendingItem);
- }
-
- @Override
- public void onChooserSelected(final int chooserIndex) {
- mHost.invalidateActionBar();
- mHost.dismissActionMode();
- }
- });
- }
-
- mMediaPicker.open(MediaPicker.MEDIA_TYPE_DEFAULT, animate);
-
- return isOpen();
- }
-
- @Override
- public boolean hide(boolean animate) {
- if (mMediaPicker != null) {
- mMediaPicker.dismiss(animate);
- }
- return !isOpen();
- }
-
- public void resetViewHolderState() {
- if (mMediaPicker != null) {
- mMediaPicker.resetViewHolderState();
- }
- }
-
- public void setConversationThemeColor(final int themeColor) {
- if (mMediaPicker != null) {
- mMediaPicker.setConversationThemeColor(themeColor);
- }
- }
-
- private boolean isOpen() {
- return (mMediaPicker != null && mMediaPicker.isOpen());
- }
-
- private MediaPicker getExistingOrCreateMediaPicker() {
- if (mMediaPicker != null) {
- return mMediaPicker;
- }
- MediaPicker mediaPicker = (MediaPicker)
- mFragmentManager.findFragmentByTag(MediaPicker.FRAGMENT_TAG);
- if (mediaPicker == null) {
- mediaPicker = mHost.createMediaPicker();
- if (mediaPicker == null) {
- return null; // this use of ComposeMessageView doesn't support media picking
- }
- mFragmentManager.beginTransaction().replace(
- R.id.mediapicker_container,
- mediaPicker,
- MediaPicker.FRAGMENT_TAG).commit();
- }
- return mediaPicker;
- }
-
- @Override
- public boolean updateActionBar(ActionBar actionBar) {
- if (isOpen()) {
- mMediaPicker.updateActionBar(actionBar);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onNavigationUpPressed() {
- if (isOpen() && mMediaPicker.isFullScreen()) {
- return onBackPressed();
- }
- return super.onNavigationUpPressed();
- }
-
- public boolean onBackPressed() {
- if (mMediaPicker != null && mMediaPicker.onBackPressed()) {
- return true;
- }
- return super.onBackPressed();
- }
- }
-
- /**
- * Manages showing/hiding the SIM selector in conversation.
- */
- private class SimSelector extends ConversationSimSelector {
- public SimSelector(ConversationInputBase baseHost) {
- super(baseHost);
- }
-
- @Override
- protected SimSelectorView getSimSelectorView() {
- return mHost.getSimSelectorView();
- }
-
- @Override
- public int getSimSelectorItemLayoutId() {
- return mHost.getSimSelectorItemLayoutId();
- }
-
- @Override
- protected void selectSim(SubscriptionListEntry item) {
- mHost.selectSim(item);
- }
-
- @Override
- public boolean show(boolean animate) {
- final boolean result = super.show(animate);
- mHost.showHideSimSelector(true /*show*/);
- return result;
- }
-
- @Override
- public boolean hide(boolean animate) {
- final boolean result = super.hide(animate);
- mHost.showHideSimSelector(false /*show*/);
- return result;
- }
- }
-
- /**
- * Manages showing/hiding the IME keyboard in conversation.
- */
- private class ConversationImeKeyboard extends ConversationInput {
- public ConversationImeKeyboard(ConversationInputBase baseHost, final boolean isShowing) {
- super(baseHost, isShowing);
- }
-
- @Override
- public boolean show(boolean animate) {
- ImeUtil.get().showImeKeyboard(mContext, mSink.getComposeEditText());
- return true;
- }
-
- @Override
- public boolean hide(boolean animate) {
- ImeUtil.get().hideImeKeyboard(mContext, mSink.getComposeEditText());
- return true;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java
deleted file mode 100644
index 2748fff..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.AsyncImageView;
-import com.android.messaging.ui.CursorRecyclerAdapter;
-import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
-import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost;
-import com.android.messaging.util.Assert;
-
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * Provides an interface to expose Conversation Message Cursor data to a UI widget like a
- * RecyclerView.
- */
-public class ConversationMessageAdapter extends
- CursorRecyclerAdapter<ConversationMessageAdapter.ConversationMessageViewHolder> {
-
- private final ConversationMessageViewHost mHost;
- private final AsyncImageViewDelayLoader mImageViewDelayLoader;
- private final View.OnClickListener mViewClickListener;
- private final View.OnLongClickListener mViewLongClickListener;
- private boolean mOneOnOne;
- private String mSelectedMessageId;
-
- public ConversationMessageAdapter(final Context context, final Cursor cursor,
- final ConversationMessageViewHost host,
- final AsyncImageViewDelayLoader imageViewDelayLoader,
- final View.OnClickListener viewClickListener,
- final View.OnLongClickListener longClickListener) {
- super(context, cursor, 0);
- mHost = host;
- mViewClickListener = viewClickListener;
- mViewLongClickListener = longClickListener;
- mImageViewDelayLoader = imageViewDelayLoader;
- setHasStableIds(true);
- }
-
- @Override
- public void bindViewHolder(final ConversationMessageViewHolder holder,
- final Context context, final Cursor cursor) {
- Assert.isTrue(holder.mView instanceof ConversationMessageView);
- final ConversationMessageView conversationMessageView =
- (ConversationMessageView) holder.mView;
- conversationMessageView.bind(cursor, mOneOnOne, mSelectedMessageId);
- }
-
- @Override
- public ConversationMessageViewHolder createViewHolder(final Context context,
- final ViewGroup parent, final int viewType) {
- final LayoutInflater layoutInflater = LayoutInflater.from(context);
- final ConversationMessageView conversationMessageView = (ConversationMessageView)
- layoutInflater.inflate(R.layout.conversation_message_view, null);
- conversationMessageView.setHost(mHost);
- conversationMessageView.setImageViewDelayLoader(mImageViewDelayLoader);
- return new ConversationMessageViewHolder(conversationMessageView,
- mViewClickListener, mViewLongClickListener);
- }
-
- public void setSelectedMessage(final String messageId) {
- mSelectedMessageId = messageId;
- notifyDataSetChanged();
- }
-
- public void setOneOnOne(final boolean oneOnOne, final boolean invalidate) {
- if (mOneOnOne != oneOnOne) {
- mOneOnOne = oneOnOne;
- if (invalidate) {
- notifyDataSetChanged();
- }
- }
- }
-
- /**
- * ViewHolder that holds a ConversationMessageView.
- */
- public static class ConversationMessageViewHolder extends RecyclerView.ViewHolder {
- final View mView;
-
- /**
- * @param viewClickListener a View.OnClickListener that should define the interaction when
- * an item in the RecyclerView is clicked.
- */
- public ConversationMessageViewHolder(final View itemView,
- final View.OnClickListener viewClickListener,
- final View.OnLongClickListener viewLongClickListener) {
- super(itemView);
- mView = itemView;
-
- mView.setOnClickListener(viewClickListener);
- mView.setOnLongClickListener(viewLongClickListener);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java b/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java
deleted file mode 100644
index ef6aeb4..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.annotation.VisibleForAnimation;
-import com.android.messaging.datamodel.data.ConversationMessageBubbleData;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Shows the message bubble for one conversation message. It is able to animate size changes
- * by morphing when the message content changes size.
- */
-// TODO: Move functionality from ConversationMessageView into this class as appropriate
-public class ConversationMessageBubbleView extends LinearLayout {
- private int mIntrinsicWidth;
- private int mMorphedWidth;
- private ObjectAnimator mAnimator;
- private boolean mShouldAnimateWidthChange;
- private final ConversationMessageBubbleData mData;
- private int mRunningStartWidth;
- private ViewGroup mBubbleBackground;
-
- public ConversationMessageBubbleView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = new ConversationMessageBubbleData();
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mBubbleBackground = (ViewGroup) findViewById(R.id.message_text_and_info);
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- final int newIntrinsicWidth = getMeasuredWidth();
- if (mIntrinsicWidth == 0 && newIntrinsicWidth != mIntrinsicWidth) {
- if (mShouldAnimateWidthChange) {
- kickOffMorphAnimation(mIntrinsicWidth, newIntrinsicWidth);
- }
- mIntrinsicWidth = newIntrinsicWidth;
- }
-
- if (mMorphedWidth > 0) {
- mBubbleBackground.getLayoutParams().width = mMorphedWidth;
- } else {
- mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
- }
- mBubbleBackground.requestLayout();
- }
-
- @VisibleForAnimation
- public void setMorphWidth(final int width) {
- mMorphedWidth = width;
- requestLayout();
- }
-
- public void bind(final ConversationMessageData data) {
- final boolean changed = mData.bind(data);
- // Animate width change only when we are binding to the same message, so that we may
- // animate view size changes on the same message bubble due to things like status text
- // change.
- // Don't animate width change when the bubble contains attachments. Width animation is
- // only suitable for text-only messages (where the bubble size change due to status or
- // time stamp changes).
- mShouldAnimateWidthChange = !changed && !data.hasAttachments();
- if (mAnimator == null) {
- mMorphedWidth = 0;
- }
- }
-
- public void kickOffMorphAnimation(final int oldWidth, final int newWidth) {
- if (mAnimator != null) {
- mAnimator.setIntValues(mRunningStartWidth, newWidth);
- return;
- }
- mRunningStartWidth = oldWidth;
- mAnimator = ObjectAnimator.ofInt(this, "morphWidth", oldWidth, newWidth);
- mAnimator.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
- mAnimator.addListener(new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animator) {
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- mAnimator = null;
- mMorphedWidth = 0;
- // Allow the bubble to resize if, for example, the status text changed during
- // the animation. This will snap to the bigger size if needed. This is intentional
- // as animating immediately after looks really bad and switching layout params
- // during the original animation does not achieve the desired effect.
- mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
- mBubbleBackground.requestLayout();
- }
-
- @Override
- public void onAnimationCancel(Animator animator) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animator) {
- }
- });
- mAnimator.start();
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
deleted file mode 100644
index e22e2c7..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationMessageView.java
+++ /dev/null
@@ -1,1206 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Formatter;
-import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.MessagePartImageRequestDescriptor;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.AsyncImageView;
-import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
-import com.android.messaging.ui.AudioAttachmentView;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.ui.MultiAttachmentLayout;
-import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
-import com.android.messaging.ui.PersonItemView;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.VideoThumbnailView;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.YouTubeUtil;
-import com.google.common.base.Predicate;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * The view for a single entry in a conversation.
- */
-public class ConversationMessageView extends FrameLayout implements View.OnClickListener,
- View.OnLongClickListener, OnAttachmentClickListener {
- public interface ConversationMessageViewHost {
- boolean onAttachmentClick(ConversationMessageView view, MessagePartData attachment,
- Rect imageBounds, boolean longPress);
- SubscriptionListEntry getSubscriptionEntryForSelfParticipant(String selfParticipantId,
- boolean excludeDefault);
- }
-
- private final ConversationMessageData mData;
-
- private LinearLayout mMessageAttachmentsView;
- private MultiAttachmentLayout mMultiAttachmentView;
- private AsyncImageView mMessageImageView;
- private TextView mMessageTextView;
- private boolean mMessageTextHasLinks;
- private boolean mMessageHasYouTubeLink;
- private TextView mStatusTextView;
- private TextView mTitleTextView;
- private TextView mMmsInfoTextView;
- private LinearLayout mMessageTitleLayout;
- private TextView mSenderNameTextView;
- private ContactIconView mContactIconView;
- private ConversationMessageBubbleView mMessageBubble;
- private View mSubjectView;
- private TextView mSubjectLabel;
- private TextView mSubjectText;
- private View mDeliveredBadge;
- private ViewGroup mMessageMetadataView;
- private ViewGroup mMessageTextAndInfoView;
- private TextView mSimNameView;
-
- private boolean mOneOnOne;
- private ConversationMessageViewHost mHost;
-
- public ConversationMessageView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- // TODO: we should switch to using Binding and DataModel factory methods.
- mData = new ConversationMessageData();
- }
-
- @Override
- protected void onFinishInflate() {
- mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon);
- mContactIconView.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View view) {
- ConversationMessageView.this.performLongClick();
- return true;
- }
- });
-
- mMessageAttachmentsView = (LinearLayout) findViewById(R.id.message_attachments);
- mMultiAttachmentView = (MultiAttachmentLayout) findViewById(R.id.multiple_attachments);
- mMultiAttachmentView.setOnAttachmentClickListener(this);
-
- mMessageImageView = (AsyncImageView) findViewById(R.id.message_image);
- mMessageImageView.setOnClickListener(this);
- mMessageImageView.setOnLongClickListener(this);
-
- mMessageTextView = (TextView) findViewById(R.id.message_text);
- mMessageTextView.setOnClickListener(this);
- IgnoreLinkLongClickHelper.ignoreLinkLongClick(mMessageTextView, this);
-
- mStatusTextView = (TextView) findViewById(R.id.message_status);
- mTitleTextView = (TextView) findViewById(R.id.message_title);
- mMmsInfoTextView = (TextView) findViewById(R.id.mms_info);
- mMessageTitleLayout = (LinearLayout) findViewById(R.id.message_title_layout);
- mSenderNameTextView = (TextView) findViewById(R.id.message_sender_name);
- mMessageBubble = (ConversationMessageBubbleView) findViewById(R.id.message_content);
- mSubjectView = findViewById(R.id.subject_container);
- mSubjectLabel = (TextView) mSubjectView.findViewById(R.id.subject_label);
- mSubjectText = (TextView) mSubjectView.findViewById(R.id.subject_text);
- mDeliveredBadge = findViewById(R.id.smsDeliveredBadge);
- mMessageMetadataView = (ViewGroup) findViewById(R.id.message_metadata);
- mMessageTextAndInfoView = (ViewGroup) findViewById(R.id.message_text_and_info);
- mSimNameView = (TextView) findViewById(R.id.sim_name);
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- final int horizontalSpace = MeasureSpec.getSize(widthMeasureSpec);
- final int iconSize = getResources()
- .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size);
-
- final int unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int iconMeasureSpec = MeasureSpec.makeMeasureSpec(iconSize, MeasureSpec.EXACTLY);
-
- mContactIconView.measure(iconMeasureSpec, iconMeasureSpec);
-
- final int arrowWidth =
- getResources().getDimensionPixelSize(R.dimen.message_bubble_arrow_width);
-
- // We need to subtract contact icon width twice from the horizontal space to get
- // the max leftover space because we want the message bubble to extend no further than the
- // starting position of the message bubble in the opposite direction.
- final int maxLeftoverSpace = horizontalSpace - mContactIconView.getMeasuredWidth() * 2
- - arrowWidth - getPaddingLeft() - getPaddingRight();
- final int messageContentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxLeftoverSpace,
- MeasureSpec.AT_MOST);
-
- mMessageBubble.measure(messageContentWidthMeasureSpec, unspecifiedMeasureSpec);
-
- final int maxHeight = Math.max(mContactIconView.getMeasuredHeight(),
- mMessageBubble.getMeasuredHeight());
- setMeasuredDimension(horizontalSpace, maxHeight + getPaddingBottom() + getPaddingTop());
- }
-
- @Override
- protected void onLayout(final boolean changed, final int left, final int top, final int right,
- final int bottom) {
- final boolean isRtl = AccessibilityUtil.isLayoutRtl(this);
-
- final int iconWidth = mContactIconView.getMeasuredWidth();
- final int iconHeight = mContactIconView.getMeasuredHeight();
- final int iconTop = getPaddingTop();
- final int contentWidth = (right -left) - iconWidth - getPaddingLeft() - getPaddingRight();
- final int contentHeight = mMessageBubble.getMeasuredHeight();
- final int contentTop = iconTop;
-
- final int iconLeft;
- final int contentLeft;
- if (mData.getIsIncoming()) {
- if (isRtl) {
- iconLeft = (right - left) - getPaddingRight() - iconWidth;
- contentLeft = iconLeft - contentWidth;
- } else {
- iconLeft = getPaddingLeft();
- contentLeft = iconLeft + iconWidth;
- }
- } else {
- if (isRtl) {
- iconLeft = getPaddingLeft();
- contentLeft = iconLeft + iconWidth;
- } else {
- iconLeft = (right - left) - getPaddingRight() - iconWidth;
- contentLeft = iconLeft - contentWidth;
- }
- }
-
- mContactIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
-
- mMessageBubble.layout(contentLeft, contentTop, contentLeft + contentWidth,
- contentTop + contentHeight);
- }
-
- /**
- * Fills in the data associated with this view.
- *
- * @param cursor The cursor from a MessageList that this view is in, pointing to its entry.
- */
- public void bind(final Cursor cursor) {
- bind(cursor, true, null);
- }
-
- /**
- * Fills in the data associated with this view.
- *
- * @param cursor The cursor from a MessageList that this view is in, pointing to its entry.
- * @param oneOnOne Whether this is a 1:1 conversation
- */
- public void bind(final Cursor cursor,
- final boolean oneOnOne, final String selectedMessageId) {
- mOneOnOne = oneOnOne;
-
- // Update our UI model
- mData.bind(cursor);
- setSelected(TextUtils.equals(mData.getMessageId(), selectedMessageId));
-
- // Update text and image content for the view.
- updateViewContent();
-
- // Update colors and layout parameters for the view.
- updateViewAppearance();
-
- updateContentDescription();
- }
-
- public void setHost(final ConversationMessageViewHost host) {
- mHost = host;
- }
-
- /**
- * Sets a delay loader instance to manage loading / resuming of image attachments.
- */
- public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
- Assert.notNull(mMessageImageView);
- mMessageImageView.setDelayLoader(delayLoader);
- mMultiAttachmentView.setImageViewDelayLoader(delayLoader);
- }
-
- public ConversationMessageData getData() {
- return mData;
- }
-
- /**
- * Returns whether we should show simplified visual style for the message view (i.e. hide the
- * avatar and bubble arrow, reduce padding).
- */
- private boolean shouldShowSimplifiedVisualStyle() {
- return mData.getCanClusterWithPreviousMessage();
- }
-
- /**
- * Returns whether we need to show message bubble arrow. We don't show arrow if the message
- * contains media attachments or if shouldShowSimplifiedVisualStyle() is true.
- */
- private boolean shouldShowMessageBubbleArrow() {
- return !shouldShowSimplifiedVisualStyle()
- && !(mData.hasAttachments() || mMessageHasYouTubeLink);
- }
-
- /**
- * Returns whether we need to show a message bubble for text content.
- */
- private boolean shouldShowMessageTextBubble() {
- if (mData.hasText()) {
- return true;
- }
- final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
- mData.getMmsSubject());
- if (!TextUtils.isEmpty(subjectText)) {
- return true;
- }
- return false;
- }
-
- private void updateViewContent() {
- updateMessageContent();
- int titleResId = -1;
- int statusResId = -1;
- String statusText = null;
- switch(mData.getStatus()) {
- case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
- titleResId = R.string.message_title_downloading;
- statusResId = R.string.message_status_downloading;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_manual_download;
- if (isSelected()) {
- statusResId = R.string.message_status_download_action;
- } else {
- statusResId = R.string.message_status_download;
- }
- }
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_download_failed;
- statusResId = R.string.message_status_download_error;
- }
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_download_failed;
- if (isSelected()) {
- statusResId = R.string.message_status_download_action;
- } else {
- statusResId = R.string.message_status_download;
- }
- }
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
- case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
- statusResId = R.string.message_status_sending;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
- case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
- statusResId = R.string.message_status_send_retrying;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
- statusResId = R.string.message_status_send_failed_emergency_number;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
- // don't show the error state unless we're the default sms app
- if (PhoneUtils.getDefault().isDefaultSmsApp()) {
- if (isSelected()) {
- statusResId = R.string.message_status_resend;
- } else {
- statusResId = MmsUtils.mapRawStatusToErrorResourceId(
- mData.getStatus(), mData.getRawTelephonyStatus());
- }
- break;
- }
- // FALL THROUGH HERE
-
- case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
- case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
- default:
- if (!mData.getCanClusterWithNextMessage()) {
- statusText = mData.getFormattedReceivedTimeStamp();
- }
- break;
- }
-
- final boolean titleVisible = (titleResId >= 0);
- if (titleVisible) {
- final String titleText = getResources().getString(titleResId);
- mTitleTextView.setText(titleText);
-
- final String mmsInfoText = getResources().getString(
- R.string.mms_info,
- Formatter.formatFileSize(getContext(), mData.getSmsMessageSize()),
- DateUtils.formatDateTime(
- getContext(),
- mData.getMmsExpiry(),
- DateUtils.FORMAT_SHOW_DATE |
- DateUtils.FORMAT_SHOW_TIME |
- DateUtils.FORMAT_NUMERIC_DATE |
- DateUtils.FORMAT_NO_YEAR));
- mMmsInfoTextView.setText(mmsInfoText);
- mMessageTitleLayout.setVisibility(View.VISIBLE);
- } else {
- mMessageTitleLayout.setVisibility(View.GONE);
- }
-
- final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
- mData.getMmsSubject());
- final boolean subjectVisible = !TextUtils.isEmpty(subjectText);
-
- final boolean senderNameVisible = !mOneOnOne && !mData.getCanClusterWithNextMessage()
- && mData.getIsIncoming();
- if (senderNameVisible) {
- mSenderNameTextView.setText(mData.getSenderDisplayName());
- mSenderNameTextView.setVisibility(View.VISIBLE);
- } else {
- mSenderNameTextView.setVisibility(View.GONE);
- }
-
- if (statusResId >= 0) {
- statusText = getResources().getString(statusResId);
- }
-
- // We set the text even if the view will be GONE for accessibility
- mStatusTextView.setText(statusText);
- final boolean statusVisible = !TextUtils.isEmpty(statusText);
- if (statusVisible) {
- mStatusTextView.setVisibility(View.VISIBLE);
- } else {
- mStatusTextView.setVisibility(View.GONE);
- }
-
- final boolean deliveredBadgeVisible =
- mData.getStatus() == MessageData.BUGLE_STATUS_OUTGOING_DELIVERED;
- mDeliveredBadge.setVisibility(deliveredBadgeVisible ? View.VISIBLE : View.GONE);
-
- // Update the sim indicator.
- final boolean showSimIconAsIncoming = mData.getIsIncoming() &&
- (!mData.hasAttachments() || shouldShowMessageTextBubble());
- final SubscriptionListEntry subscriptionEntry =
- mHost.getSubscriptionEntryForSelfParticipant(mData.getSelfParticipantId(),
- true /* excludeDefault */);
- final boolean simNameVisible = subscriptionEntry != null &&
- !TextUtils.isEmpty(subscriptionEntry.displayName) &&
- !mData.getCanClusterWithNextMessage();
- if (simNameVisible) {
- final String simNameText = mData.getIsIncoming() ? getResources().getString(
- R.string.incoming_sim_name_text, subscriptionEntry.displayName) :
- subscriptionEntry.displayName;
- mSimNameView.setText(simNameText);
- mSimNameView.setTextColor(showSimIconAsIncoming ? getResources().getColor(
- R.color.timestamp_text_incoming) : subscriptionEntry.displayColor);
- mSimNameView.setVisibility(VISIBLE);
- } else {
- mSimNameView.setText(null);
- mSimNameView.setVisibility(GONE);
- }
-
- final boolean metadataVisible = senderNameVisible || statusVisible
- || deliveredBadgeVisible || simNameVisible;
- mMessageMetadataView.setVisibility(metadataVisible ? View.VISIBLE : View.GONE);
-
- final boolean messageTextAndOrInfoVisible = titleVisible || subjectVisible
- || mData.hasText() || metadataVisible;
- mMessageTextAndInfoView.setVisibility(
- messageTextAndOrInfoVisible ? View.VISIBLE : View.GONE);
-
- if (shouldShowSimplifiedVisualStyle()) {
- mContactIconView.setVisibility(View.GONE);
- mContactIconView.setImageResourceUri(null);
- } else {
- mContactIconView.setVisibility(View.VISIBLE);
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- mData.getSenderProfilePhotoUri(),
- mData.getSenderFullName(),
- mData.getSenderNormalizedDestination(),
- mData.getSenderContactLookupKey());
- mContactIconView.setImageResourceUri(avatarUri, mData.getSenderContactId(),
- mData.getSenderContactLookupKey(), mData.getSenderNormalizedDestination());
- }
- }
-
- private void updateMessageContent() {
- // We must update the text before the attachments since we search the text to see if we
- // should make a preview youtube image in the attachments
- updateMessageText();
- updateMessageAttachments();
- updateMessageSubject();
- mMessageBubble.bind(mData);
- }
-
- private void updateMessageAttachments() {
- // Bind video, audio, and VCard attachments. If there are multiple, they stack vertically.
- bindAttachmentsOfSameType(sVideoFilter,
- R.layout.message_video_attachment, mVideoViewBinder, VideoThumbnailView.class);
- bindAttachmentsOfSameType(sAudioFilter,
- R.layout.message_audio_attachment, mAudioViewBinder, AudioAttachmentView.class);
- bindAttachmentsOfSameType(sVCardFilter,
- R.layout.message_vcard_attachment, mVCardViewBinder, PersonItemView.class);
-
- // Bind image attachments. If there are multiple, they are shown in a collage view.
- final List<MessagePartData> imageParts = mData.getAttachments(sImageFilter);
- if (imageParts.size() > 1) {
- Collections.sort(imageParts, sImageComparator);
- mMultiAttachmentView.bindAttachments(imageParts, null, imageParts.size());
- mMultiAttachmentView.setVisibility(View.VISIBLE);
- } else {
- mMultiAttachmentView.setVisibility(View.GONE);
- }
-
- // In the case that we have no image attachments and exactly one youtube link in a message
- // then we will show a preview.
- String youtubeThumbnailUrl = null;
- String originalYoutubeLink = null;
- if (mMessageTextHasLinks && imageParts.size() == 0) {
- CharSequence messageTextWithSpans = mMessageTextView.getText();
- final URLSpan[] spans = ((Spanned) messageTextWithSpans).getSpans(0,
- messageTextWithSpans.length(), URLSpan.class);
- for (URLSpan span : spans) {
- String url = span.getURL();
- String youtubeLinkForUrl = YouTubeUtil.getYoutubePreviewImageLink(url);
- if (!TextUtils.isEmpty(youtubeLinkForUrl)) {
- if (TextUtils.isEmpty(youtubeThumbnailUrl)) {
- // Save the youtube link if we don't already have one
- youtubeThumbnailUrl = youtubeLinkForUrl;
- originalYoutubeLink = url;
- } else {
- // We already have a youtube link. This means we have two youtube links so
- // we shall show none.
- youtubeThumbnailUrl = null;
- originalYoutubeLink = null;
- break;
- }
- }
- }
- }
- // We need to keep track if we have a youtube link in the message so that we will not show
- // the arrow
- mMessageHasYouTubeLink = !TextUtils.isEmpty(youtubeThumbnailUrl);
-
- // We will show the message image view if there is one attachment or one youtube link
- if (imageParts.size() == 1 || mMessageHasYouTubeLink) {
- // Get the display metrics for a hint for how large to pull the image data into
- final WindowManager windowManager = (WindowManager) getContext().
- getSystemService(Context.WINDOW_SERVICE);
- final DisplayMetrics displayMetrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getMetrics(displayMetrics);
-
- final int iconSize = getResources()
- .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size);
- final int desiredWidth = displayMetrics.widthPixels - iconSize - iconSize;
-
- if (imageParts.size() == 1) {
- final MessagePartData imagePart = imageParts.get(0);
- // If the image is big, we want to scale it down to save memory since we're going to
- // scale it down to fit into the bubble width. We don't constrain the height.
- final ImageRequestDescriptor imageRequest =
- new MessagePartImageRequestDescriptor(imagePart,
- desiredWidth,
- MessagePartData.UNSPECIFIED_SIZE,
- false);
- adjustImageViewBounds(imagePart);
- mMessageImageView.setImageResourceId(imageRequest);
- mMessageImageView.setTag(imagePart);
- } else {
- // Youtube Thumbnail image
- final ImageRequestDescriptor imageRequest =
- new UriImageRequestDescriptor(Uri.parse(youtubeThumbnailUrl), desiredWidth,
- MessagePartData.UNSPECIFIED_SIZE, true /* allowCompression */,
- true /* isStatic */, false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- mMessageImageView.setImageResourceId(imageRequest);
- mMessageImageView.setTag(originalYoutubeLink);
- }
- mMessageImageView.setVisibility(View.VISIBLE);
- } else {
- mMessageImageView.setImageResourceId(null);
- mMessageImageView.setVisibility(View.GONE);
- }
-
- // Show the message attachments container if any of its children are visible
- boolean attachmentsVisible = false;
- for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
- final View attachmentView = mMessageAttachmentsView.getChildAt(i);
- if (attachmentView.getVisibility() == View.VISIBLE) {
- attachmentsVisible = true;
- break;
- }
- }
- mMessageAttachmentsView.setVisibility(attachmentsVisible ? View.VISIBLE : View.GONE);
- }
-
- private void bindAttachmentsOfSameType(final Predicate<MessagePartData> attachmentTypeFilter,
- final int attachmentViewLayoutRes, final AttachmentViewBinder viewBinder,
- final Class<?> attachmentViewClass) {
- final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
-
- // Iterate through all attachments of a particular type (video, audio, etc).
- // Find the first attachment index that matches the given type if possible.
- int attachmentViewIndex = -1;
- View existingAttachmentView;
- do {
- existingAttachmentView = mMessageAttachmentsView.getChildAt(++attachmentViewIndex);
- } while (existingAttachmentView != null &&
- !(attachmentViewClass.isInstance(existingAttachmentView)));
-
- for (final MessagePartData attachment : mData.getAttachments(attachmentTypeFilter)) {
- View attachmentView = mMessageAttachmentsView.getChildAt(attachmentViewIndex);
- if (!attachmentViewClass.isInstance(attachmentView)) {
- attachmentView = layoutInflater.inflate(attachmentViewLayoutRes,
- mMessageAttachmentsView, false /* attachToRoot */);
- attachmentView.setOnClickListener(this);
- attachmentView.setOnLongClickListener(this);
- mMessageAttachmentsView.addView(attachmentView, attachmentViewIndex);
- }
- viewBinder.bindView(attachmentView, attachment);
- attachmentView.setTag(attachment);
- attachmentView.setVisibility(View.VISIBLE);
- attachmentViewIndex++;
- }
- // If there are unused views left over, unbind or remove them.
- while (attachmentViewIndex < mMessageAttachmentsView.getChildCount()) {
- final View attachmentView = mMessageAttachmentsView.getChildAt(attachmentViewIndex);
- if (attachmentViewClass.isInstance(attachmentView)) {
- mMessageAttachmentsView.removeViewAt(attachmentViewIndex);
- } else {
- // No more views of this type; we're done.
- break;
- }
- }
- }
-
- private void updateMessageSubject() {
- final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
- mData.getMmsSubject());
- final boolean subjectVisible = !TextUtils.isEmpty(subjectText);
-
- if (subjectVisible) {
- mSubjectText.setText(subjectText);
- mSubjectView.setVisibility(View.VISIBLE);
- } else {
- mSubjectView.setVisibility(View.GONE);
- }
- }
-
- private void updateMessageText() {
- final String text = mData.getText();
- if (!TextUtils.isEmpty(text)) {
- mMessageTextView.setText(text);
- // Linkify phone numbers, web urls, emails, and map addresses to allow users to
- // click on them and take the default intent.
- mMessageTextHasLinks = Linkify.addLinks(mMessageTextView, Linkify.ALL);
- mMessageTextView.setVisibility(View.VISIBLE);
- } else {
- mMessageTextView.setVisibility(View.GONE);
- mMessageTextHasLinks = false;
- }
- }
-
- private void updateViewAppearance() {
- final Resources res = getResources();
- final ConversationDrawables drawableProvider = ConversationDrawables.get();
- final boolean incoming = mData.getIsIncoming();
- final boolean outgoing = !incoming;
- final boolean showArrow = shouldShowMessageBubbleArrow();
-
- final int messageTopPaddingClustered =
- res.getDimensionPixelSize(R.dimen.message_padding_same_author);
- final int messageTopPaddingDefault =
- res.getDimensionPixelSize(R.dimen.message_padding_default);
- final int arrowWidth = res.getDimensionPixelOffset(R.dimen.message_bubble_arrow_width);
- final int messageTextMinHeightDefault = res.getDimensionPixelSize(
- R.dimen.conversation_message_contact_icon_size);
- final int messageTextLeftRightPadding = res.getDimensionPixelOffset(
- R.dimen.message_text_left_right_padding);
- final int textTopPaddingDefault = res.getDimensionPixelOffset(
- R.dimen.message_text_top_padding);
- final int textBottomPaddingDefault = res.getDimensionPixelOffset(
- R.dimen.message_text_bottom_padding);
-
- // These values depend on whether the message has text, attachments, or both.
- // We intentionally don't set defaults, so the compiler will tell us if we forget
- // to set one of them, or if we set one more than once.
- final int contentLeftPadding, contentRightPadding;
- final Drawable textBackground;
- final int textMinHeight;
- final int textTopMargin;
- final int textTopPadding, textBottomPadding;
- final int textLeftPadding, textRightPadding;
-
- if (mData.hasAttachments()) {
- if (shouldShowMessageTextBubble()) {
- // Text and attachment(s)
- contentLeftPadding = incoming ? arrowWidth : 0;
- contentRightPadding = outgoing ? arrowWidth : 0;
- textBackground = drawableProvider.getBubbleDrawable(
- isSelected(),
- incoming,
- false /* needArrow */,
- mData.hasIncomingErrorStatus());
- textMinHeight = messageTextMinHeightDefault;
- textTopMargin = messageTopPaddingClustered;
- textTopPadding = textTopPaddingDefault;
- textBottomPadding = textBottomPaddingDefault;
- textLeftPadding = messageTextLeftRightPadding;
- textRightPadding = messageTextLeftRightPadding;
- } else {
- // Attachment(s) only
- contentLeftPadding = incoming ? arrowWidth : 0;
- contentRightPadding = outgoing ? arrowWidth : 0;
- textBackground = null;
- textMinHeight = 0;
- textTopMargin = 0;
- textTopPadding = 0;
- textBottomPadding = 0;
- textLeftPadding = 0;
- textRightPadding = 0;
- }
- } else {
- // Text only
- contentLeftPadding = (!showArrow && incoming) ? arrowWidth : 0;
- contentRightPadding = (!showArrow && outgoing) ? arrowWidth : 0;
- textBackground = drawableProvider.getBubbleDrawable(
- isSelected(),
- incoming,
- shouldShowMessageBubbleArrow(),
- mData.hasIncomingErrorStatus());
- textMinHeight = messageTextMinHeightDefault;
- textTopMargin = 0;
- textTopPadding = textTopPaddingDefault;
- textBottomPadding = textBottomPaddingDefault;
- if (showArrow && incoming) {
- textLeftPadding = messageTextLeftRightPadding + arrowWidth;
- } else {
- textLeftPadding = messageTextLeftRightPadding;
- }
- if (showArrow && outgoing) {
- textRightPadding = messageTextLeftRightPadding + arrowWidth;
- } else {
- textRightPadding = messageTextLeftRightPadding;
- }
- }
-
- // These values do not depend on whether the message includes attachments
- final int gravity = incoming ? (Gravity.START | Gravity.CENTER_VERTICAL) :
- (Gravity.END | Gravity.CENTER_VERTICAL);
- final int messageTopPadding = shouldShowSimplifiedVisualStyle() ?
- messageTopPaddingClustered : messageTopPaddingDefault;
- final int metadataTopPadding = res.getDimensionPixelOffset(
- R.dimen.message_metadata_top_padding);
-
- // Update the message text/info views
- ImageUtils.setBackgroundDrawableOnView(mMessageTextAndInfoView, textBackground);
- mMessageTextAndInfoView.setMinimumHeight(textMinHeight);
- final LinearLayout.LayoutParams textAndInfoLayoutParams =
- (LinearLayout.LayoutParams) mMessageTextAndInfoView.getLayoutParams();
- textAndInfoLayoutParams.topMargin = textTopMargin;
-
- if (UiUtils.isRtlMode()) {
- // Need to switch right and left padding in RtL mode
- mMessageTextAndInfoView.setPadding(textRightPadding, textTopPadding, textLeftPadding,
- textBottomPadding);
- mMessageBubble.setPadding(contentRightPadding, 0, contentLeftPadding, 0);
- } else {
- mMessageTextAndInfoView.setPadding(textLeftPadding, textTopPadding, textRightPadding,
- textBottomPadding);
- mMessageBubble.setPadding(contentLeftPadding, 0, contentRightPadding, 0);
- }
-
- // Update the message row and message bubble views
- setPadding(getPaddingLeft(), messageTopPadding, getPaddingRight(), 0);
- mMessageBubble.setGravity(gravity);
- updateMessageAttachmentsAppearance(gravity);
-
- mMessageMetadataView.setPadding(0, metadataTopPadding, 0, 0);
-
- updateTextAppearance();
-
- requestLayout();
- }
-
- private void updateContentDescription() {
- StringBuilder description = new StringBuilder();
-
- Resources res = getResources();
- String separator = res.getString(R.string.enumeration_comma);
-
- // Sender information
- boolean hasPlainTextMessage = !(TextUtils.isEmpty(mData.getText()) ||
- mMessageTextHasLinks);
- if (mData.getIsIncoming()) {
- int senderResId = hasPlainTextMessage
- ? R.string.incoming_text_sender_content_description
- : R.string.incoming_sender_content_description;
- description.append(res.getString(senderResId, mData.getSenderDisplayName()));
- } else {
- int senderResId = hasPlainTextMessage
- ? R.string.outgoing_text_sender_content_description
- : R.string.outgoing_sender_content_description;
- description.append(res.getString(senderResId));
- }
-
- if (mSubjectView.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(mSubjectText.getText());
- }
-
- if (mMessageTextView.getVisibility() == View.VISIBLE) {
- // If the message has hyperlinks, we will let the user navigate to the text message so
- // that the hyperlink can be clicked. Otherwise, the text message does not need to
- // be reachable.
- if (mMessageTextHasLinks) {
- mMessageTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- } else {
- mMessageTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- description.append(separator);
- description.append(mMessageTextView.getText());
- }
- }
-
- if (mMessageTitleLayout.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(mTitleTextView.getText());
-
- description.append(separator);
- description.append(mMmsInfoTextView.getText());
- }
-
- if (mStatusTextView.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(mStatusTextView.getText());
- }
-
- if (mSimNameView.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(mSimNameView.getText());
- }
-
- if (mDeliveredBadge.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(res.getString(R.string.delivered_status_content_description));
- }
-
- setContentDescription(description);
- }
-
- private void updateMessageAttachmentsAppearance(final int gravity) {
- mMessageAttachmentsView.setGravity(gravity);
-
- // Tint image/video attachments when selected
- final int selectedImageTint = getResources().getColor(R.color.message_image_selected_tint);
- if (mMessageImageView.getVisibility() == View.VISIBLE) {
- if (isSelected()) {
- mMessageImageView.setColorFilter(selectedImageTint);
- } else {
- mMessageImageView.clearColorFilter();
- }
- }
- if (mMultiAttachmentView.getVisibility() == View.VISIBLE) {
- if (isSelected()) {
- mMultiAttachmentView.setColorFilter(selectedImageTint);
- } else {
- mMultiAttachmentView.clearColorFilter();
- }
- }
- for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
- final View attachmentView = mMessageAttachmentsView.getChildAt(i);
- if (attachmentView instanceof VideoThumbnailView
- && attachmentView.getVisibility() == View.VISIBLE) {
- final VideoThumbnailView videoView = (VideoThumbnailView) attachmentView;
- if (isSelected()) {
- videoView.setColorFilter(selectedImageTint);
- } else {
- videoView.clearColorFilter();
- }
- }
- }
-
- // If there are multiple attachment bubbles in a single message, add some separation.
- final int multipleAttachmentPadding =
- getResources().getDimensionPixelSize(R.dimen.message_padding_same_author);
-
- boolean previousVisibleView = false;
- for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
- final View attachmentView = mMessageAttachmentsView.getChildAt(i);
- if (attachmentView.getVisibility() == View.VISIBLE) {
- final int margin = previousVisibleView ? multipleAttachmentPadding : 0;
- ((LinearLayout.LayoutParams) attachmentView.getLayoutParams()).topMargin = margin;
- // updateViewAppearance calls requestLayout() at the end, so we don't need to here
- previousVisibleView = true;
- }
- }
- }
-
- private void updateTextAppearance() {
- int messageColorResId;
- int statusColorResId = -1;
- int infoColorResId = -1;
- int timestampColorResId;
- int subjectLabelColorResId;
- if (isSelected()) {
- messageColorResId = R.color.message_text_color_incoming;
- statusColorResId = R.color.message_action_status_text;
- infoColorResId = R.color.message_action_info_text;
- if (shouldShowMessageTextBubble()) {
- timestampColorResId = R.color.message_action_timestamp_text;
- subjectLabelColorResId = R.color.message_action_timestamp_text;
- } else {
- // If there's no text, the timestamp will be shown below the attachments,
- // against the conversation view background.
- timestampColorResId = R.color.timestamp_text_outgoing;
- subjectLabelColorResId = R.color.timestamp_text_outgoing;
- }
- } else {
- messageColorResId = (mData.getIsIncoming() ?
- R.color.message_text_color_incoming : R.color.message_text_color_outgoing);
- statusColorResId = messageColorResId;
- infoColorResId = R.color.timestamp_text_incoming;
- switch(mData.getStatus()) {
-
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
- timestampColorResId = R.color.message_failed_timestamp_text;
- subjectLabelColorResId = R.color.timestamp_text_outgoing;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
- case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
- case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
- case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
- case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
- case MessageData.BUGLE_STATUS_OUTGOING_DELIVERED:
- timestampColorResId = R.color.timestamp_text_outgoing;
- subjectLabelColorResId = R.color.timestamp_text_outgoing;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
- case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
- messageColorResId = R.color.message_text_color_incoming_download_failed;
- timestampColorResId = R.color.message_download_failed_timestamp_text;
- subjectLabelColorResId = R.color.message_text_color_incoming_download_failed;
- statusColorResId = R.color.message_download_failed_status_text;
- infoColorResId = R.color.message_info_text_incoming_download_failed;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
- case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
- timestampColorResId = R.color.message_text_color_incoming;
- subjectLabelColorResId = R.color.message_text_color_incoming;
- infoColorResId = R.color.timestamp_text_incoming;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
- default:
- timestampColorResId = R.color.timestamp_text_incoming;
- subjectLabelColorResId = R.color.timestamp_text_incoming;
- infoColorResId = -1; // Not used
- break;
- }
- }
- final int messageColor = getResources().getColor(messageColorResId);
- mMessageTextView.setTextColor(messageColor);
- mMessageTextView.setLinkTextColor(messageColor);
- mSubjectText.setTextColor(messageColor);
- if (statusColorResId >= 0) {
- mTitleTextView.setTextColor(getResources().getColor(statusColorResId));
- }
- if (infoColorResId >= 0) {
- mMmsInfoTextView.setTextColor(getResources().getColor(infoColorResId));
- }
- if (timestampColorResId == R.color.timestamp_text_incoming &&
- mData.hasAttachments() && !shouldShowMessageTextBubble()) {
- timestampColorResId = R.color.timestamp_text_outgoing;
- }
- mStatusTextView.setTextColor(getResources().getColor(timestampColorResId));
-
- mSubjectLabel.setTextColor(getResources().getColor(subjectLabelColorResId));
- mSenderNameTextView.setTextColor(getResources().getColor(timestampColorResId));
- }
-
- /**
- * If we don't know the size of the image, we want to show it in a fixed-sized frame to
- * avoid janks when the image is loaded and resized. Otherwise, we can set the imageview to
- * take on normal layout params.
- */
- private void adjustImageViewBounds(final MessagePartData imageAttachment) {
- Assert.isTrue(ContentType.isImageType(imageAttachment.getContentType()));
- final ViewGroup.LayoutParams layoutParams = mMessageImageView.getLayoutParams();
- if (imageAttachment.getWidth() == MessagePartData.UNSPECIFIED_SIZE ||
- imageAttachment.getHeight() == MessagePartData.UNSPECIFIED_SIZE) {
- // We don't know the size of the image attachment, enable letterboxing on the image
- // and show a fixed sized attachment. This should happen at most once per image since
- // after the image is loaded we then save the image dimensions to the db so that the
- // next time we can display the full size.
- layoutParams.width = getResources()
- .getDimensionPixelSize(R.dimen.image_attachment_fallback_width);
- layoutParams.height = getResources()
- .getDimensionPixelSize(R.dimen.image_attachment_fallback_height);
- mMessageImageView.setScaleType(ScaleType.CENTER_CROP);
- } else {
- layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- // ScaleType.CENTER_INSIDE and FIT_CENTER behave similarly for most images. However,
- // FIT_CENTER works better for small images as it enlarges the image such that the
- // minimum size ("android:minWidth" etc) is honored.
- mMessageImageView.setScaleType(ScaleType.FIT_CENTER);
- }
- }
-
- @Override
- public void onClick(final View view) {
- final Object tag = view.getTag();
- if (tag instanceof MessagePartData) {
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
- onAttachmentClick((MessagePartData) tag, bounds, false /* longPress */);
- } else if (tag instanceof String) {
- // Currently the only object that would make a tag of a string is a youtube preview
- // image
- UIIntents.get().launchBrowserForUrl(getContext(), (String) tag);
- }
- }
-
- @Override
- public boolean onLongClick(final View view) {
- if (view == mMessageTextView) {
- // Preemptively handle the long click event on message text so it's not handled by
- // the link spans.
- return performLongClick();
- }
-
- final Object tag = view.getTag();
- if (tag instanceof MessagePartData) {
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
- return onAttachmentClick((MessagePartData) tag, bounds, true /* longPress */);
- }
-
- return false;
- }
-
- @Override
- public boolean onAttachmentClick(final MessagePartData attachment,
- final Rect viewBoundsOnScreen, final boolean longPress) {
- return mHost.onAttachmentClick(this, attachment, viewBoundsOnScreen, longPress);
- }
-
- public ContactIconView getContactIconView() {
- return mContactIconView;
- }
-
- // Sort photos in MultiAttachLayout in the same order as the ConversationImagePartsView
- static final Comparator<MessagePartData> sImageComparator = new Comparator<MessagePartData>(){
- @Override
- public int compare(final MessagePartData x, final MessagePartData y) {
- return x.getPartId().compareTo(y.getPartId());
- }
- };
-
- static final Predicate<MessagePartData> sVideoFilter = new Predicate<MessagePartData>() {
- @Override
- public boolean apply(final MessagePartData part) {
- return part.isVideo();
- }
- };
-
- static final Predicate<MessagePartData> sAudioFilter = new Predicate<MessagePartData>() {
- @Override
- public boolean apply(final MessagePartData part) {
- return part.isAudio();
- }
- };
-
- static final Predicate<MessagePartData> sVCardFilter = new Predicate<MessagePartData>() {
- @Override
- public boolean apply(final MessagePartData part) {
- return part.isVCard();
- }
- };
-
- static final Predicate<MessagePartData> sImageFilter = new Predicate<MessagePartData>() {
- @Override
- public boolean apply(final MessagePartData part) {
- return part.isImage();
- }
- };
-
- interface AttachmentViewBinder {
- void bindView(View view, MessagePartData attachment);
- void unbind(View view);
- }
-
- final AttachmentViewBinder mVideoViewBinder = new AttachmentViewBinder() {
- @Override
- public void bindView(final View view, final MessagePartData attachment) {
- ((VideoThumbnailView) view).setSource(attachment, mData.getIsIncoming());
- }
-
- @Override
- public void unbind(final View view) {
- ((VideoThumbnailView) view).setSource((Uri) null, mData.getIsIncoming());
- }
- };
-
- final AttachmentViewBinder mAudioViewBinder = new AttachmentViewBinder() {
- @Override
- public void bindView(final View view, final MessagePartData attachment) {
- final AudioAttachmentView audioView = (AudioAttachmentView) view;
- audioView.bindMessagePartData(attachment, isSelected() || mData.getIsIncoming());
- audioView.setBackground(ConversationDrawables.get().getBubbleDrawable(
- isSelected(), mData.getIsIncoming(), false /* needArrow */,
- mData.hasIncomingErrorStatus()));
- }
-
- @Override
- public void unbind(final View view) {
- ((AudioAttachmentView) view).bindMessagePartData(null, mData.getIsIncoming());
- }
- };
-
- final AttachmentViewBinder mVCardViewBinder = new AttachmentViewBinder() {
- @Override
- public void bindView(final View view, final MessagePartData attachment) {
- final PersonItemView personView = (PersonItemView) view;
- personView.bind(DataModel.get().createVCardContactItemData(getContext(),
- attachment));
- personView.setBackground(ConversationDrawables.get().getBubbleDrawable(
- isSelected(), mData.getIsIncoming(), false /* needArrow */,
- mData.hasIncomingErrorStatus()));
- final int nameTextColorRes;
- final int detailsTextColorRes;
- if (isSelected()) {
- nameTextColorRes = R.color.message_text_color_incoming;
- detailsTextColorRes = R.color.message_text_color_incoming;
- } else {
- nameTextColorRes = mData.getIsIncoming() ? R.color.message_text_color_incoming
- : R.color.message_text_color_outgoing;
- detailsTextColorRes = mData.getIsIncoming() ? R.color.timestamp_text_incoming
- : R.color.timestamp_text_outgoing;
- }
- personView.setNameTextColor(getResources().getColor(nameTextColorRes));
- personView.setDetailsTextColor(getResources().getColor(detailsTextColorRes));
- }
-
- @Override
- public void unbind(final View view) {
- ((PersonItemView) view).bind(null);
- }
- };
-
- /**
- * A helper class that allows us to handle long clicks on linkified message text view (i.e. to
- * select the message) so it's not handled by the link spans to launch apps for the links.
- */
- private static class IgnoreLinkLongClickHelper implements OnLongClickListener, OnTouchListener {
- private boolean mIsLongClick;
- private final OnLongClickListener mDelegateLongClickListener;
-
- /**
- * Ignore long clicks on linkified texts for a given text view.
- * @param textView the TextView to ignore long clicks on
- * @param longClickListener a delegate OnLongClickListener to be called when the view is
- * long clicked.
- */
- public static void ignoreLinkLongClick(final TextView textView,
- @Nullable final OnLongClickListener longClickListener) {
- final IgnoreLinkLongClickHelper helper =
- new IgnoreLinkLongClickHelper(longClickListener);
- textView.setOnLongClickListener(helper);
- textView.setOnTouchListener(helper);
- }
-
- private IgnoreLinkLongClickHelper(@Nullable final OnLongClickListener longClickListener) {
- mDelegateLongClickListener = longClickListener;
- }
-
- @Override
- public boolean onLongClick(final View v) {
- // Record that this click is a long click.
- mIsLongClick = true;
- if (mDelegateLongClickListener != null) {
- return mDelegateLongClickListener.onLongClick(v);
- }
- return false;
- }
-
- @Override
- public boolean onTouch(final View v, final MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP && mIsLongClick) {
- // This touch event is a long click, preemptively handle this touch event so that
- // the link span won't get a onClicked() callback.
- mIsLongClick = false;
- return true;
- }
-
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mIsLongClick = false;
- }
- return false;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationSimSelector.java b/src/com/android/messaging/ui/conversation/ConversationSimSelector.java
deleted file mode 100644
index fc43a46..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationSimSelector.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.content.Context;
-import android.support.v4.util.Pair;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.SubscriptionListData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.ui.conversation.SimSelectorView.SimSelectorViewListener;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.ThreadUtil;
-
-/**
- * Manages showing/hiding the SIM selector in conversation.
- */
-abstract class ConversationSimSelector extends ConversationInput {
- private SimSelectorView mSimSelectorView;
- private Pair<Boolean /* show */, Boolean /* animate */> mPendingShow;
- private boolean mDataReady;
- private String mSelectedSimText;
-
- public ConversationSimSelector(ConversationInputBase baseHost) {
- super(baseHost, false);
- }
-
- public void onSubscriptionListDataLoaded(final SubscriptionListData subscriptionListData) {
- ensureSimSelectorView();
- mSimSelectorView.bind(subscriptionListData);
- mDataReady = subscriptionListData != null && subscriptionListData.hasData();
- if (mPendingShow != null && mDataReady) {
- Assert.isTrue(OsUtil.isAtLeastL_MR1());
- final boolean show = mPendingShow.first;
- final boolean animate = mPendingShow.second;
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- // This will No-Op if we are no longer attached to the host.
- mConversationInputBase.showHideInternal(ConversationSimSelector.this,
- show, animate);
- }
- });
- mPendingShow = null;
- }
- }
-
- private void announcedSelectedSim() {
- final Context context = Factory.get().getApplicationContext();
- if (AccessibilityUtil.isTouchExplorationEnabled(context) &&
- !TextUtils.isEmpty(mSelectedSimText)) {
- AccessibilityUtil.announceForAccessibilityCompat(
- mSimSelectorView, null,
- context.getString(R.string.selected_sim_content_message, mSelectedSimText));
- }
- }
-
- public void setSelected(final SubscriptionListEntry subEntry) {
- mSelectedSimText = subEntry == null ? null : subEntry.displayName;
- }
-
- @Override
- public boolean show(boolean animate) {
- announcedSelectedSim();
- return showHide(true, animate);
- }
-
- @Override
- public boolean hide(boolean animate) {
- return showHide(false, animate);
- }
-
- private boolean showHide(final boolean show, final boolean animate) {
- if (!OsUtil.isAtLeastL_MR1()) {
- return false;
- }
-
- if (mDataReady) {
- mSimSelectorView.showOrHide(show, animate);
- return mSimSelectorView.isOpen() == show;
- } else {
- mPendingShow = Pair.create(show, animate);
- return false;
- }
- }
-
- private void ensureSimSelectorView() {
- if (mSimSelectorView == null) {
- // Grab the SIM selector view from the host. This class assumes ownership of it.
- mSimSelectorView = getSimSelectorView();
- mSimSelectorView.setItemLayoutId(getSimSelectorItemLayoutId());
- mSimSelectorView.setListener(new SimSelectorViewListener() {
-
- @Override
- public void onSimSelectorVisibilityChanged(boolean visible) {
- onVisibilityChanged(visible);
- }
-
- @Override
- public void onSimItemClicked(SubscriptionListEntry item) {
- selectSim(item);
- }
- });
- }
- }
-
- protected abstract SimSelectorView getSimSelectorView();
- protected abstract void selectSim(final SubscriptionListEntry item);
- protected abstract int getSimSelectorItemLayoutId();
-
-}
diff --git a/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java b/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java
deleted file mode 100644
index e3ad601..0000000
--- a/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.widget.EditText;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.ParticipantRefresh;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.UiUtils;
-
-/**
- * The dialog for the user to enter the phone number of their sim.
- */
-public class EnterSelfPhoneNumberDialog extends DialogFragment {
- private EditText mEditText;
- private int mSubId;
-
- public static EnterSelfPhoneNumberDialog newInstance(final int subId) {
- final EnterSelfPhoneNumberDialog dialog = new EnterSelfPhoneNumberDialog();
- dialog.mSubId = subId;
- return dialog;
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final Context context = getActivity();
- final LayoutInflater inflater = LayoutInflater.from(context);
- mEditText = (EditText) inflater.inflate(R.layout.enter_phone_number_view, null, false);
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.enter_phone_number_title)
- .setMessage(R.string.enter_phone_number_text)
- .setView(mEditText)
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- dismiss();
- }
- })
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- final String newNumber = mEditText.getText().toString();
- dismiss();
- if (!TextUtils.isEmpty(newNumber)) {
- savePhoneNumberInPrefs(newNumber);
- // TODO: Remove this toast and just auto-send
- // the message instead
- UiUtils.showToast(
- R.string
- .toast_after_setting_default_sms_app_for_message_send);
- }
- }
- });
- return builder.create();
- }
-
- private void savePhoneNumberInPrefs(final String newPhoneNumber) {
- final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId);
- subPrefs.putString(getString(R.string.mms_phone_number_pref_key),
- newPhoneNumber);
- // Update the self participants so the new phone number will be reflected
- // everywhere in the UI.
- ParticipantRefresh.refreshSelfParticipants();
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java b/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
deleted file mode 100644
index 8af9f75..0000000
--- a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversation;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.LaunchConversationData;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-
-/**
- * Launches ConversationActivity for sending a message to, or viewing messages from, a specific
- * recipient.
- * <p>
- * (This activity should be marked noHistory="true" in AndroidManifest.xml)
- */
-public class LaunchConversationActivity extends Activity implements
- LaunchConversationData.LaunchConversationDataListener {
- static final String SMS_BODY = "sms_body";
- static final String ADDRESS = "address";
- final Binding<LaunchConversationData> mBinding = BindingBase.createBinding(this);
- String mSmsBody;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
- return;
- }
-
- final Intent intent = getIntent();
- final String action = intent.getAction();
- if (Intent.ACTION_SENDTO.equals(action) || Intent.ACTION_VIEW.equals(action)) {
- String[] recipients = UriUtil.parseRecipientsFromSmsMmsUri(intent.getData());
- final boolean haveAddress = !TextUtils.isEmpty(intent.getStringExtra(ADDRESS));
- final boolean haveEmail = !TextUtils.isEmpty(intent.getStringExtra(Intent.EXTRA_EMAIL));
- if (recipients == null && (haveAddress || haveEmail)) {
- if (haveAddress) {
- recipients = new String[] { intent.getStringExtra(ADDRESS) };
- } else {
- recipients = new String[] { intent.getStringExtra(Intent.EXTRA_EMAIL) };
- }
- }
- mSmsBody = intent.getStringExtra(SMS_BODY);
- if (TextUtils.isEmpty(mSmsBody)) {
- // Used by intents sent from the web YouTube (and perhaps others).
- mSmsBody = getBody(intent.getData());
- if (TextUtils.isEmpty(mSmsBody)) {
- // If that fails, try yet another method apps use to share text
- if (ContentType.TEXT_PLAIN.equals(intent.getType())) {
- mSmsBody = intent.getStringExtra(Intent.EXTRA_TEXT);
- }
- }
- }
- if (recipients != null) {
- mBinding.bind(DataModel.get().createLaunchConversationData(this));
- mBinding.getData().getOrCreateConversation(mBinding, recipients);
- } else {
- // No recipients were specified in the intent.
- // Start a new conversation with contact picker. The new conversation will be
- // primed with the (optional) message in mSmsBody.
- onGetOrCreateNewConversation(null);
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Unsupported conversation intent action : " + action);
- }
- // As of M, activities without a visible window must finish before onResume completes.
- finish();
- }
-
- private String getBody(final Uri uri) {
- if (uri == null) {
- return null;
- }
- String urlStr = uri.getSchemeSpecificPart();
- if (!urlStr.contains("?")) {
- return null;
- }
- urlStr = urlStr.substring(urlStr.indexOf('?') + 1);
- final String[] params = urlStr.split("&");
- for (final String p : params) {
- if (p.startsWith("body=")) {
- try {
- return URLDecoder.decode(p.substring(5), "UTF-8");
- } catch (final UnsupportedEncodingException e) {
- // Invalid URL, ignore
- }
- }
- }
- return null;
- }
-
- @Override
- public void onGetOrCreateNewConversation(final String conversationId) {
- final Context context = Factory.get().getApplicationContext();
- UIIntents.get().launchConversationActivityWithParentStack(context, conversationId,
- mSmsBody);
- }
-
- @Override
- public void onGetOrCreateNewConversationFailed() {
- UiUtils.showToastAtBottom(R.string.conversation_creation_failure);
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java b/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java
deleted file mode 100644
index 4c22970..0000000
--- a/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversation;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-
-public class MessageBubbleBackground extends LinearLayout {
- private final int mSnapWidthPixels;
-
- public MessageBubbleBackground(Context context, AttributeSet attrs) {
- super(context, attrs);
- mSnapWidthPixels = context.getResources().getDimensionPixelSize(
- R.dimen.conversation_bubble_width_snap);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- final int widthPadding = getPaddingLeft() + getPaddingRight();
- int bubbleWidth = getMeasuredWidth() - widthPadding;
- final int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - widthPadding;
- // Round up to next snapWidthPixels
- bubbleWidth = Math.min(maxWidth,
- (int) (Math.ceil(bubbleWidth / (float) mSnapWidthPixels) * mSnapWidthPixels));
- super.onMeasure(
- MeasureSpec.makeMeasureSpec(bubbleWidth + widthPadding, MeasureSpec.EXACTLY),
- heightMeasureSpec);
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java b/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java
deleted file mode 100644
index 89b9148..0000000
--- a/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversation;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.text.format.Formatter;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.ConversationParticipantsData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.sms.DatabaseMessages.MmsMessage;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-
-import java.util.List;
-
-public class MessageDetailsDialog {
- private static final String RECIPIENT_SEPARATOR = ", ";
-
- // All methods are static, no creating this class
- private MessageDetailsDialog() {
- }
-
- public static void show(final Context context, final ConversationMessageData data,
- final ConversationParticipantsData participants, final ParticipantData self) {
- if (DebugUtils.isDebugEnabled()) {
- new SafeAsyncTask<Void, Void, String>() {
- @Override
- protected String doInBackgroundTimed(Void... params) {
- return getMessageDetails(context, data, participants, self);
- }
-
- @Override
- protected void onPostExecute(String messageDetails) {
- showDialog(context, messageDetails);
- }
- }.executeOnThreadPool(null, null, null);
- } else {
- String messageDetails = getMessageDetails(context, data, participants, self);
- showDialog(context, messageDetails);
- }
- }
-
- private static String getMessageDetails(final Context context,
- final ConversationMessageData data,
- final ConversationParticipantsData participants, final ParticipantData self) {
- String messageDetails = null;
- if (data.getIsSms()) {
- messageDetails = getSmsMessageDetails(data, participants, self);
- } else {
- // TODO: Handle SMS_TYPE_MMS_PUSH_NOTIFICATION type differently?
- messageDetails = getMmsMessageDetails(context, data, participants, self);
- }
-
- return messageDetails;
- }
-
- private static void showDialog(final Context context, String messageDetails) {
- if (!TextUtils.isEmpty(messageDetails)) {
- new AlertDialog.Builder(context)
- .setTitle(R.string.message_details_title)
- .setMessage(messageDetails)
- .setCancelable(true)
- .show();
- }
- }
-
- /**
- * Return a string, separated by newlines, that contains a number of labels and values
- * for this sms message. The string will be displayed in a modal dialog.
- * @return string list of various message properties
- */
- private static String getSmsMessageDetails(final ConversationMessageData data,
- final ConversationParticipantsData participants, final ParticipantData self) {
- final Resources res = Factory.get().getApplicationContext().getResources();
- final StringBuilder details = new StringBuilder();
-
- // Type: Text message
- details.append(res.getString(R.string.message_type_label));
- details.append(res.getString(R.string.text_message));
-
- // From: +1425xxxxxxx
- // or To: +1425xxxxxxx
- final String rawSender = data.getSenderNormalizedDestination();
- if (!TextUtils.isEmpty(rawSender)) {
- details.append('\n');
- details.append(res.getString(R.string.from_label));
- details.append(rawSender);
- }
- final String rawRecipients = getRecipientParticipantString(participants,
- data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
- if (!TextUtils.isEmpty(rawRecipients)) {
- details.append('\n');
- details.append(res.getString(R.string.to_address_label));
- details.append(rawRecipients);
- }
-
- // Sent: Mon 11:42AM
- if (data.getIsIncoming()) {
- if (data.getSentTimeStamp() != MmsUtils.INVALID_TIMESTAMP) {
- details.append('\n');
- details.append(res.getString(R.string.sent_label));
- details.append(
- Dates.getMessageDetailsTimeString(data.getSentTimeStamp()).toString());
- }
- }
-
- // Sent: Mon 11:43AM
- // or Received: Mon 11:43AM
- appendSentOrReceivedTimestamp(res, details, data);
-
- appendSimInfo(res, self, details);
-
- if (DebugUtils.isDebugEnabled()) {
- appendDebugInfo(details, data);
- }
-
- return details.toString();
- }
-
- /**
- * Return a string, separated by newlines, that contains a number of labels and values
- * for this mms message. The string will be displayed in a modal dialog.
- * @return string list of various message properties
- */
- private static String getMmsMessageDetails(Context context, final ConversationMessageData data,
- final ConversationParticipantsData participants, final ParticipantData self) {
- final Resources res = Factory.get().getApplicationContext().getResources();
- // TODO: when we support non-auto-download of mms messages, we'll have to handle
- // the case when the message is a PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND and display
- // something different. See the Messaging app's MessageUtils.getNotificationIndDetails()
-
- final StringBuilder details = new StringBuilder();
-
- // Type: Multimedia message.
- details.append(res.getString(R.string.message_type_label));
- details.append(res.getString(R.string.multimedia_message));
-
- // From: +1425xxxxxxx
- final String rawSender = data.getSenderNormalizedDestination();
- details.append('\n');
- details.append(res.getString(R.string.from_label));
- details.append(!TextUtils.isEmpty(rawSender) ? rawSender :
- res.getString(R.string.hidden_sender_address));
-
- // To: +1425xxxxxxx
- final String rawRecipients = getRecipientParticipantString(participants,
- data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
- if (!TextUtils.isEmpty(rawRecipients)) {
- details.append('\n');
- details.append(res.getString(R.string.to_address_label));
- details.append(rawRecipients);
- }
-
- // Sent: Tue 3:05PM
- // or Received: Tue 3:05PM
- appendSentOrReceivedTimestamp(res, details, data);
-
- // Subject: You're awesome
- details.append('\n');
- details.append(res.getString(R.string.subject_label));
- if (!TextUtils.isEmpty(MmsUtils.cleanseMmsSubject(res, data.getMmsSubject()))) {
- details.append(data.getMmsSubject());
- }
-
- // Priority: High/Normal/Low
- details.append('\n');
- details.append(res.getString(R.string.priority_label));
- details.append(getPriorityDescription(res, data.getSmsPriority()));
-
- // Message size: 30 KB
- if (data.getSmsMessageSize() > 0) {
- details.append('\n');
- details.append(res.getString(R.string.message_size_label));
- details.append(Formatter.formatFileSize(context, data.getSmsMessageSize()));
- }
-
- appendSimInfo(res, self, details);
-
- if (DebugUtils.isDebugEnabled()) {
- appendDebugInfo(details, data);
- }
-
- return details.toString();
- }
-
- private static void appendSentOrReceivedTimestamp(Resources res, StringBuilder details,
- ConversationMessageData data) {
- int labelId = -1;
- if (data.getIsIncoming()) {
- labelId = R.string.received_label;
- } else if (data.getIsSendComplete()) {
- labelId = R.string.sent_label;
- }
- if (labelId >= 0) {
- details.append('\n');
- details.append(res.getString(labelId));
- details.append(
- Dates.getMessageDetailsTimeString(data.getReceivedTimeStamp()).toString());
- }
- }
-
- @DoesNotRunOnMainThread
- private static void appendDebugInfo(StringBuilder details, ConversationMessageData data) {
- // We grab the thread id from the database, so this needs to run in the background
- Assert.isNotMainThread();
- details.append("\n\n");
- details.append("DEBUG");
-
- details.append('\n');
- details.append("Message id: ");
- details.append(data.getMessageId());
-
- final String telephonyUri = data.getSmsMessageUri();
- details.append('\n');
- details.append("Telephony uri: ");
- details.append(telephonyUri);
-
- final String conversationId = data.getConversationId();
-
- if (conversationId == null) {
- return;
- }
-
- details.append('\n');
- details.append("Conversation id: ");
- details.append(conversationId);
-
- final long threadId = BugleDatabaseOperations.getThreadId(DataModel.get().getDatabase(),
- conversationId);
-
- details.append('\n');
- details.append("Conversation telephony thread id: ");
- details.append(threadId);
-
- MmsMessage mms = null;
-
- if (data.getIsMms()) {
- if (telephonyUri == null) {
- return;
- }
- mms = MmsUtils.loadMms(Uri.parse(telephonyUri));
- if (mms == null) {
- return;
- }
-
- // We log the thread id again to check that they are internally consistent
- final long mmsThreadId = mms.mThreadId;
- details.append('\n');
- details.append("Telephony thread id: ");
- details.append(mmsThreadId);
-
- // Log the MMS content location
- final String mmsContentLocation = mms.mContentLocation;
- details.append('\n');
- details.append("Content location URL: ");
- details.append(mmsContentLocation);
- }
-
- final String recipientsString = MmsUtils.getRawRecipientIdsForThread(threadId);
- if (recipientsString != null) {
- details.append('\n');
- details.append("Thread recipient ids: ");
- details.append(recipientsString);
- }
-
- final List<String> recipients = MmsUtils.getRecipientsByThread(threadId);
- if (recipients != null) {
- details.append('\n');
- details.append("Thread recipients: ");
- details.append(recipients.toString());
-
- if (mms != null) {
- final String from = MmsUtils.getMmsSender(recipients, mms.getUri());
- details.append('\n');
- details.append("Sender: ");
- details.append(from);
- }
- }
- }
-
- private static String getRecipientParticipantString(
- final ConversationParticipantsData participants, final String senderId,
- final boolean addSelf, final String selfId) {
- final StringBuilder recipients = new StringBuilder();
- for (final ParticipantData participant : participants) {
- if (TextUtils.equals(participant.getId(), senderId)) {
- // Don't add sender
- continue;
- }
- if (participant.isSelf() &&
- (!participant.getId().equals(selfId) || !addSelf)) {
- // For self participants, don't add the one that's not relevant to this message
- // or if we are asked not to add self
- continue;
- }
- final String phoneNumber = participant.getNormalizedDestination();
- // Don't add empty number. This should not happen. But if that happens
- // we should not add it.
- if (!TextUtils.isEmpty(phoneNumber)) {
- if (recipients.length() > 0) {
- recipients.append(RECIPIENT_SEPARATOR);
- }
- recipients.append(phoneNumber);
- }
- }
- return recipients.toString();
- }
-
- /**
- * Convert the numeric mms priority into a human-readable string
- * @param res
- * @param priorityValue coded PduHeader priority
- * @return string representation of the priority
- */
- private static String getPriorityDescription(final Resources res, final int priorityValue) {
- switch(priorityValue) {
- case PduHeaders.PRIORITY_HIGH:
- return res.getString(R.string.priority_high);
- case PduHeaders.PRIORITY_LOW:
- return res.getString(R.string.priority_low);
- case PduHeaders.PRIORITY_NORMAL:
- default:
- return res.getString(R.string.priority_normal);
- }
- }
-
- private static void appendSimInfo(final Resources res,
- final ParticipantData self, final StringBuilder outString) {
- if (!OsUtil.isAtLeastL_MR1()
- || self == null
- || PhoneUtils.getDefault().getActiveSubscriptionCount() < 2) {
- return;
- }
- // The appended SIM info would look like:
- // SIM: SUB 01
- // or SIM: SIM 1
- // or SIM: Unknown
- Assert.isTrue(self.isSelf());
- outString.append('\n');
- outString.append(res.getString(R.string.sim_label));
- if (self.isActiveSubscription() && !self.isDefaultSelf()) {
- final String subscriptionName = self.getSubscriptionName();
- if (TextUtils.isEmpty(subscriptionName)) {
- outString.append(res.getString(R.string.sim_slot_identifier,
- self.getDisplaySlotId()));
- } else {
- outString.append(subscriptionName);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/SimIconView.java b/src/com/android/messaging/ui/conversation/SimIconView.java
deleted file mode 100644
index e2e446c..0000000
--- a/src/com/android/messaging/ui/conversation/SimIconView.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.content.Context;
-import android.graphics.Outline;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Shows SIM avatar icon in the SIM switcher / Self-send button.
- */
-public class SimIconView extends ContactIconView {
- public SimIconView(Context context, AttributeSet attrs) {
- super(context, attrs);
- if (OsUtil.isAtLeastL()) {
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View v, Outline outline) {
- outline.setOval(0, 0, v.getWidth(), v.getHeight());
- }
- });
- }
- }
-
- @Override
- protected void maybeInitializeOnClickListener() {
- // TODO: SIM icon view shouldn't consume or handle clicks, but it should if
- // this is the send button for the only SIM in the device or if MSIM is not supported.
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/SimSelectorItemView.java b/src/com/android/messaging/ui/conversation/SimSelectorItemView.java
deleted file mode 100644
index 3058d31..0000000
--- a/src/com/android/messaging/ui/conversation/SimSelectorItemView.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.util.Assert;
-
-/**
- * Shows a view for a SIM in the SIM selector.
- */
-public class SimSelectorItemView extends LinearLayout {
- public interface HostInterface {
- void onSimItemClicked(SubscriptionListEntry item);
- }
-
- private SubscriptionListEntry mData;
- private TextView mNameTextView;
- private TextView mDetailsTextView;
- private SimIconView mSimIconView;
- private HostInterface mHost;
-
- public SimSelectorItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- mNameTextView = (TextView) findViewById(R.id.name);
- mDetailsTextView = (TextView) findViewById(R.id.details);
- mSimIconView = (SimIconView) findViewById(R.id.sim_icon);
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mHost.onSimItemClicked(mData);
- }
- });
- }
-
- public void bind(final SubscriptionListEntry simEntry) {
- Assert.notNull(simEntry);
- mData = simEntry;
- updateViewAppearance();
- }
-
- public void setHostInterface(final HostInterface host) {
- mHost = host;
- }
-
- private void updateViewAppearance() {
- Assert.notNull(mData);
- final String displayName = mData.displayName;
- if (TextUtils.isEmpty(displayName)) {
- mNameTextView.setVisibility(GONE);
- } else {
- mNameTextView.setVisibility(VISIBLE);
- mNameTextView.setText(displayName);
- }
-
- final String details = mData.displayDestination;
- if (TextUtils.isEmpty(details)) {
- mDetailsTextView.setVisibility(GONE);
- } else {
- mDetailsTextView.setVisibility(VISIBLE);
- mDetailsTextView.setText(details);
- }
-
- mSimIconView.setImageResourceUri(mData.iconUri);
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/SimSelectorView.java b/src/com/android/messaging/ui/conversation/SimSelectorView.java
deleted file mode 100644
index b07ff19..0000000
--- a/src/com/android/messaging/ui/conversation/SimSelectorView.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversation;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.TranslateAnimation;
-import android.widget.ArrayAdapter;
-import android.widget.FrameLayout;
-import android.widget.ListView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.SubscriptionListData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Displays a SIM selector above the compose message view and overlays the message list.
- */
-public class SimSelectorView extends FrameLayout implements SimSelectorItemView.HostInterface {
- public interface SimSelectorViewListener {
- void onSimItemClicked(SubscriptionListEntry item);
- void onSimSelectorVisibilityChanged(boolean visible);
- }
-
- private ListView mSimListView;
- private final SimSelectorAdapter mAdapter;
- private boolean mShow;
- private SimSelectorViewListener mListener;
- private int mItemLayoutId;
-
- public SimSelectorView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mAdapter = new SimSelectorAdapter(getContext());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSimListView = (ListView) findViewById(R.id.sim_list);
- mSimListView.setAdapter(mAdapter);
-
- // Clicking anywhere outside the switcher list should dismiss.
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- showOrHide(false, true);
- }
- });
- }
-
- public void bind(final SubscriptionListData data) {
- mAdapter.bindData(data.getActiveSubscriptionEntriesExcludingDefault());
- }
-
- public void setItemLayoutId(final int layoutId) {
- mItemLayoutId = layoutId;
- }
-
- public void setListener(final SimSelectorViewListener listener) {
- mListener = listener;
- }
-
- public void toggleVisibility() {
- showOrHide(!mShow, true);
- }
-
- public void showOrHide(final boolean show, final boolean animate) {
- final boolean oldShow = mShow;
- mShow = show && mAdapter.getCount() > 1;
- if (oldShow != mShow) {
- if (mListener != null) {
- mListener.onSimSelectorVisibilityChanged(mShow);
- }
-
- if (animate) {
- // Fade in the background pane.
- setVisibility(VISIBLE);
- setAlpha(mShow ? 0.0f : 1.0f);
- animate().alpha(mShow ? 1.0f : 0.0f)
- .setDuration(UiUtils.REVEAL_ANIMATION_DURATION)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- setAlpha(1.0f);
- setVisibility(mShow ? VISIBLE : GONE);
- }
- });
- } else {
- setVisibility(mShow ? VISIBLE : GONE);
- }
-
- // Slide in the SIM selector list via a translate animation.
- mSimListView.setVisibility(mShow ? VISIBLE : GONE);
- if (animate) {
- mSimListView.clearAnimation();
- final TranslateAnimation translateAnimation = new TranslateAnimation(
- Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,
- Animation.RELATIVE_TO_SELF, mShow ? 1.0f : 0.0f,
- Animation.RELATIVE_TO_SELF, mShow ? 0.0f : 1.0f);
- translateAnimation.setInterpolator(UiUtils.EASE_OUT_INTERPOLATOR);
- translateAnimation.setDuration(UiUtils.REVEAL_ANIMATION_DURATION);
- mSimListView.startAnimation(translateAnimation);
- }
- }
- }
-
- /**
- * An adapter that takes a list of SubscriptionListEntry and displays them as a list of
- * available SIMs in the SIM selector.
- */
- private class SimSelectorAdapter extends ArrayAdapter<SubscriptionListEntry> {
- public SimSelectorAdapter(final Context context) {
- super(context, R.layout.sim_selector_item_view, new ArrayList<SubscriptionListEntry>());
- }
-
- public void bindData(final List<SubscriptionListEntry> newList) {
- clear();
- addAll(newList);
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- SimSelectorItemView itemView;
- if (convertView != null && convertView instanceof SimSelectorItemView) {
- itemView = (SimSelectorItemView) convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = (SimSelectorItemView) inflater.inflate(mItemLayoutId,
- parent, false);
- itemView.setHostInterface(SimSelectorView.this);
- }
- itemView.bind(getItem(position));
- return itemView;
- }
- }
-
- @Override
- public void onSimItemClicked(SubscriptionListEntry item) {
- mListener.onSimItemClicked(item);
- showOrHide(false, true);
- }
-
- public boolean isOpen() {
- return mShow;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java
deleted file mode 100644
index dbbbb15..0000000
--- a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationlist;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.action.DeleteConversationAction;
-import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
-import com.android.messaging.datamodel.action.UpdateConversationOptionsAction;
-import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.SnackBar;
-import com.android.messaging.ui.SnackBarInteraction;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
-import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
-import com.android.messaging.ui.conversationlist.MultiSelectActionModeCallback.SelectedConversation;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.Trace;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import javax.annotation.Nullable;
-
-/**
- * Base class for many Conversation List activities. This will handle the common actions of multi
- * select and common launching of intents.
- */
-public abstract class AbstractConversationListActivity extends BugleActionBarActivity
- implements ConversationListFragmentHost, MultiSelectActionModeCallback.Listener {
-
- private static final int REQUEST_SET_DEFAULT_SMS_APP = 1;
-
- protected ConversationListFragment mConversationListFragment;
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- Trace.beginSection("AbstractConversationListActivity.onAttachFragment");
- // Fragment could be debug dialog
- if (fragment instanceof ConversationListFragment) {
- mConversationListFragment = (ConversationListFragment) fragment;
- mConversationListFragment.setHost(this);
- }
- Trace.endSection();
- }
-
- @Override
- public void onBackPressed() {
- // If action mode is active dismiss it
- if (getActionMode() != null) {
- dismissActionMode();
- return;
- }
- super.onBackPressed();
- }
-
- protected void startMultiSelectActionMode() {
- startActionMode(new MultiSelectActionModeCallback(this));
- }
-
- protected void exitMultiSelectState() {
- mConversationListFragment.showFab();
- dismissActionMode();
- mConversationListFragment.updateUi();
- }
-
- protected boolean isInConversationListSelectMode() {
- return getActionModeCallback() instanceof MultiSelectActionModeCallback;
- }
-
- @Override
- public boolean isSelectionMode() {
- return isInConversationListSelectMode();
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- }
-
- @Override
- public void onActionBarDelete(final Collection<SelectedConversation> conversations) {
- if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
- // TODO: figure out a good way to combine this with the implementation in
- // ConversationFragment doing similar things
- final Activity activity = this;
- UiUtils.showSnackBarWithCustomAction(this,
- getWindow().getDecorView().getRootView(),
- getString(R.string.requires_default_sms_app),
- SnackBar.Action.createCustomAction(new Runnable() {
- @Override
- public void run() {
- final Intent intent =
- UIIntents.get().getChangeDefaultSmsAppIntent(activity);
- startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
- }
- },
- getString(R.string.requires_default_sms_change_button)),
- null /* interactions */,
- null /* placement */);
- return;
- }
-
- new AlertDialog.Builder(this)
- .setTitle(getResources().getQuantityString(
- R.plurals.delete_conversations_confirmation_dialog_title,
- conversations.size()))
- .setPositiveButton(R.string.delete_conversation_confirmation_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- for (final SelectedConversation conversation : conversations) {
- DeleteConversationAction.deleteConversation(
- conversation.conversationId,
- conversation.timestamp);
- }
- exitMultiSelectState();
- }
- })
- .setNegativeButton(R.string.delete_conversation_decline_button, null)
- .show();
- }
-
- @Override
- public void onActionBarArchive(final Iterable<SelectedConversation> conversations,
- final boolean isToArchive) {
- final ArrayList<String> conversationIds = new ArrayList<String>();
- for (final SelectedConversation conversation : conversations) {
- final String conversationId = conversation.conversationId;
- conversationIds.add(conversationId);
- if (isToArchive) {
- UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
- } else {
- UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
- }
- }
-
- final Runnable undoRunnable = new Runnable() {
- @Override
- public void run() {
- for (final String conversationId : conversationIds) {
- if (isToArchive) {
- UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
- } else {
- UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
- }
- }
- }
- };
-
- final int textId =
- isToArchive ? R.string.archived_toast_message : R.string.unarchived_toast_message;
- final String message = getResources().getString(textId, conversationIds.size());
- UiUtils.showSnackBar(this, findViewById(android.R.id.list), message, undoRunnable,
- SnackBar.Action.SNACK_BAR_UNDO,
- mConversationListFragment.getSnackBarInteractions());
- exitMultiSelectState();
- }
-
- @Override
- public void onActionBarNotification(final Iterable<SelectedConversation> conversations,
- final boolean isNotificationOn) {
- for (final SelectedConversation conversation : conversations) {
- UpdateConversationOptionsAction.enableConversationNotifications(
- conversation.conversationId, isNotificationOn);
- }
-
- final int textId = isNotificationOn ?
- R.string.notification_on_toast_message : R.string.notification_off_toast_message;
- final String message = getResources().getString(textId, 1);
- UiUtils.showSnackBar(this, findViewById(android.R.id.list), message,
- null /* undoRunnable */,
- SnackBar.Action.SNACK_BAR_UNDO, mConversationListFragment.getSnackBarInteractions());
- exitMultiSelectState();
- }
-
- @Override
- public void onActionBarAddContact(final SelectedConversation conversation) {
- final Uri avatarUri;
- if (conversation.icon != null) {
- avatarUri = Uri.parse(conversation.icon);
- } else {
- avatarUri = null;
- }
- final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog(
- this, avatarUri, conversation.otherParticipantNormalizedDestination);
- dialog.show();
- exitMultiSelectState();
- }
-
- @Override
- public void onActionBarBlock(final SelectedConversation conversation) {
- final Resources res = getResources();
- new AlertDialog.Builder(this)
- .setTitle(res.getString(R.string.block_confirmation_title,
- conversation.otherParticipantNormalizedDestination))
- .setMessage(res.getString(R.string.block_confirmation_message))
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface arg0, final int arg1) {
- final Context context = AbstractConversationListActivity.this;
- final View listView = findViewById(android.R.id.list);
- final List<SnackBarInteraction> interactions =
- mConversationListFragment.getSnackBarInteractions();
- final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
- undoListener =
- new UpdateDestinationBlockedActionSnackBar(
- context, listView, null /* undoRunnable */,
- interactions);
- final Runnable undoRunnable = new Runnable() {
- @Override
- public void run() {
- UpdateDestinationBlockedAction.updateDestinationBlocked(
- conversation.otherParticipantNormalizedDestination, false,
- conversation.conversationId,
- undoListener);
- }
- };
- final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
- listener = new UpdateDestinationBlockedActionSnackBar(
- context, listView, undoRunnable, interactions);
- UpdateDestinationBlockedAction.updateDestinationBlocked(
- conversation.otherParticipantNormalizedDestination, true,
- conversation.conversationId,
- listener);
- exitMultiSelectState();
- }
- })
- .create()
- .show();
- }
-
- @Override
- public void onConversationClick(final ConversationListData listData,
- final ConversationListItemData conversationListItemData,
- final boolean isLongClick,
- final ConversationListItemView conversationView) {
- if (isLongClick && !isInConversationListSelectMode()) {
- startMultiSelectActionMode();
- }
-
- if (isInConversationListSelectMode()) {
- final MultiSelectActionModeCallback multiSelectActionMode =
- (MultiSelectActionModeCallback) getActionModeCallback();
- multiSelectActionMode.toggleSelect(listData, conversationListItemData);
- mConversationListFragment.updateUi();
- } else {
- final String conversationId = conversationListItemData.getConversationId();
- Bundle sceneTransitionAnimationOptions = null;
- boolean hasCustomTransitions = false;
-
- UIIntents.get().launchConversationActivity(
- this, conversationId, null,
- sceneTransitionAnimationOptions,
- hasCustomTransitions);
- }
- }
-
- @Override
- public void onCreateConversationClick() {
- UIIntents.get().launchCreateNewConversationActivity(this, null);
- }
-
-
- @Override
- public boolean isConversationSelected(final String conversationId) {
- return isInConversationListSelectMode() &&
- ((MultiSelectActionModeCallback) getActionModeCallback()).isSelected(
- conversationId);
- }
-
- public void onActionBarDebug() {
- DebugUtils.showDebugOptions(this);
- }
-
- private static class UpdateDestinationBlockedActionSnackBar
- implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener {
- private final Context mContext;
- private final View mParentView;
- private final Runnable mUndoRunnable;
- private final List<SnackBarInteraction> mInteractions;
-
- UpdateDestinationBlockedActionSnackBar(final Context context,
- @NonNull final View parentView, @Nullable final Runnable undoRunnable,
- @Nullable List<SnackBarInteraction> interactions) {
- mContext = context;
- mParentView = parentView;
- mUndoRunnable = undoRunnable;
- mInteractions = interactions;
- }
-
- @Override
- public void onUpdateDestinationBlockedAction(
- final UpdateDestinationBlockedAction action,
- final boolean success, final boolean block,
- final String destination) {
- if (success) {
- final int messageId = block ? R.string.blocked_toast_message
- : R.string.unblocked_toast_message;
- final String message = mContext.getResources().getString(messageId, 1);
- UiUtils.showSnackBar(mContext, mParentView, message, mUndoRunnable,
- SnackBar.Action.SNACK_BAR_UNDO, mInteractions);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java
deleted file mode 100644
index 366c7d3..0000000
--- a/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationlist;
-
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.util.DebugUtils;
-
-public class ArchivedConversationListActivity extends AbstractConversationListActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final ConversationListFragment fragment =
- ConversationListFragment.createArchivedConversationListFragment();
- getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit();
- invalidateActionBar();
- }
-
- protected void updateActionBar(ActionBar actionBar) {
- actionBar.setTitle(getString(R.string.archived_activity_title));
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setBackgroundDrawable(new ColorDrawable(
- getResources().getColor(
- R.color.archived_conversation_action_bar_background_color_dark)));
- actionBar.show();
- super.updateActionBar(actionBar);
- }
-
- @Override
- public void onBackPressed() {
- if (isInConversationListSelectMode()) {
- exitMultiSelectState();
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (super.onCreateOptionsMenu(menu)) {
- return true;
- }
- getMenuInflater().inflate(R.menu.archived_conversation_list_menu, menu);
- final MenuItem item = menu.findItem(R.id.action_debug_options);
- if (item != null) {
- final boolean enableDebugItems = DebugUtils.isDebugEnabled();
- item.setVisible(enableDebugItems).setEnabled(enableDebugItems);
- }
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem menuItem) {
- switch(menuItem.getItemId()) {
- case R.id.action_debug_options:
- onActionBarDebug();
- return true;
- case android.R.id.home:
- onActionBarHome();
- return true;
- default:
- return super.onOptionsItemSelected(menuItem);
- }
- }
-
- @Override
- public void onActionBarHome() {
- onBackPressed();
- }
-
- @Override
- public boolean isSwipeAnimatable() {
- return false;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java
deleted file mode 100644
index f8abe81..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversationlist;
-
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.Trace;
-
-public class ConversationListActivity extends AbstractConversationListActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- Trace.beginSection("ConversationListActivity.onCreate");
- super.onCreate(savedInstanceState);
- setContentView(R.layout.conversation_list_activity);
- Trace.endSection();
- invalidateActionBar();
- }
-
- @Override
- protected void updateActionBar(final ActionBar actionBar) {
- actionBar.setTitle(getString(R.string.app_name));
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(false);
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
- actionBar.setBackgroundDrawable(new ColorDrawable(
- getResources().getColor(R.color.action_bar_background_color)));
- actionBar.show();
- super.updateActionBar(actionBar);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- // Invalidate the menu as items that are based on settings may have changed
- // while not in the app (e.g. Talkback enabled/disable affects new conversation
- // button)
- supportInvalidateOptionsMenu();
- }
-
- @Override
- public void onBackPressed() {
- if (isInConversationListSelectMode()) {
- exitMultiSelectState();
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- if (super.onCreateOptionsMenu(menu)) {
- return true;
- }
- getMenuInflater().inflate(R.menu.conversation_list_fragment_menu, menu);
- final MenuItem item = menu.findItem(R.id.action_debug_options);
- if (item != null) {
- final boolean enableDebugItems = DebugUtils.isDebugEnabled();
- item.setVisible(enableDebugItems).setEnabled(enableDebugItems);
- }
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem menuItem) {
- switch(menuItem.getItemId()) {
- case R.id.action_start_new_conversation:
- onActionBarStartNewConversation();
- return true;
- case R.id.action_settings:
- onActionBarSettings();
- return true;
- case R.id.action_debug_options:
- onActionBarDebug();
- return true;
- case R.id.action_show_archived:
- onActionBarArchived();
- return true;
- case R.id.action_show_blocked_contacts:
- onActionBarBlockedParticipants();
- return true;
- }
- return super.onOptionsItemSelected(menuItem);
- }
-
- @Override
- public void onActionBarHome() {
- exitMultiSelectState();
- }
-
- public void onActionBarStartNewConversation() {
- UIIntents.get().launchCreateNewConversationActivity(this, null);
- }
-
- public void onActionBarSettings() {
- UIIntents.get().launchSettingsActivity(this);
- }
-
- public void onActionBarBlockedParticipants() {
- UIIntents.get().launchBlockedParticipantsActivity(this);
- }
-
- public void onActionBarArchived() {
- UIIntents.get().launchArchivedConversationsActivity(this);
- }
-
- @Override
- public boolean isSwipeAnimatable() {
- return !isInConversationListSelectMode();
- }
-
- @Override
- public void onWindowFocusChanged(final boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- final ConversationListFragment conversationListFragment =
- (ConversationListFragment) getFragmentManager().findFragmentById(
- R.id.conversation_list_fragment);
- // When the screen is turned on, the last used activity gets resumed, but it gets
- // window focus only after the lock screen is unlocked.
- if (hasFocus && conversationListFragment != null) {
- conversationListFragment.setScrolledToNewestConversationIfNeeded();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java b/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java
deleted file mode 100644
index 629c4ae..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversationlist;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.CursorRecyclerAdapter;
-import com.android.messaging.ui.conversationlist.ConversationListItemView.HostInterface;
-
-/**
- * Provides an interface to expose Conversation List Cursor data to a UI widget like a ListView.
- */
-public class ConversationListAdapter
- extends CursorRecyclerAdapter<ConversationListAdapter.ConversationListViewHolder> {
-
- private final ConversationListItemView.HostInterface mClivHostInterface;
-
- public ConversationListAdapter(final Context context, final Cursor cursor,
- final ConversationListItemView.HostInterface clivHostInterface) {
- super(context, cursor, 0);
- mClivHostInterface = clivHostInterface;
- setHasStableIds(true);
- }
-
- /**
- * @see com.android.messaging.ui.CursorRecyclerAdapter#bindViewHolder(
- * android.support.v7.widget.RecyclerView.ViewHolder, android.content.Context,
- * android.database.Cursor)
- */
- @Override
- public void bindViewHolder(final ConversationListViewHolder holder, final Context context,
- final Cursor cursor) {
- final ConversationListItemView conversationListItemView = holder.mView;
- conversationListItemView.bind(cursor, mClivHostInterface);
- }
-
- @Override
- public ConversationListViewHolder createViewHolder(final Context context,
- final ViewGroup parent, final int viewType) {
- final LayoutInflater layoutInflater = LayoutInflater.from(context);
- final ConversationListItemView itemView =
- (ConversationListItemView) layoutInflater.inflate(
- R.layout.conversation_list_item_view, null);
- return new ConversationListViewHolder(itemView);
- }
-
- /**
- * ViewHolder that holds a ConversationListItemView.
- */
- public static class ConversationListViewHolder extends RecyclerView.ViewHolder {
- final ConversationListItemView mView;
-
- public ConversationListViewHolder(final ConversationListItemView itemView) {
- super(itemView);
- mView = itemView;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java b/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java
deleted file mode 100644
index 2f868d4..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationlist;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewGroupCompat;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.ViewPropertyAnimator;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.AbsListView;
-import android.widget.ImageView;
-
-import com.android.messaging.R;
-import com.android.messaging.annotation.VisibleForAnimation;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.ui.BugleAnimationTags;
-import com.android.messaging.ui.ListEmptyView;
-import com.android.messaging.ui.SnackBarInteraction;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows a list of conversations.
- */
-public class ConversationListFragment extends Fragment implements ConversationListDataListener,
- ConversationListItemView.HostInterface {
- private static final String BUNDLE_ARCHIVED_MODE = "archived_mode";
- private static final String BUNDLE_FORWARD_MESSAGE_MODE = "forward_message_mode";
- private static final boolean VERBOSE = false;
-
- private MenuItem mShowBlockedMenuItem;
- private boolean mArchiveMode;
- private boolean mBlockedAvailable;
- private boolean mForwardMessageMode;
-
- public interface ConversationListFragmentHost {
- public void onConversationClick(final ConversationListData listData,
- final ConversationListItemData conversationListItemData,
- final boolean isLongClick,
- final ConversationListItemView conversationView);
- public void onCreateConversationClick();
- public boolean isConversationSelected(final String conversationId);
- public boolean isSwipeAnimatable();
- public boolean isSelectionMode();
- public boolean hasWindowFocus();
- }
-
- private ConversationListFragmentHost mHost;
- private RecyclerView mRecyclerView;
- private ImageView mStartNewConversationButton;
- private ListEmptyView mEmptyListMessageView;
- private ConversationListAdapter mAdapter;
-
- // Saved Instance State Data - only for temporal data which is nice to maintain but not
- // critical for correctness.
- private static final String SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY =
- "conversationListViewState";
- private Parcelable mListState;
-
- @VisibleForTesting
- final Binding<ConversationListData> mListBinding = BindingBase.createBinding(this);
-
- public static ConversationListFragment createArchivedConversationListFragment() {
- return createConversationListFragment(BUNDLE_ARCHIVED_MODE);
- }
-
- public static ConversationListFragment createForwardMessageConversationListFragment() {
- return createConversationListFragment(BUNDLE_FORWARD_MESSAGE_MODE);
- }
-
- public static ConversationListFragment createConversationListFragment(String modeKeyName) {
- final ConversationListFragment fragment = new ConversationListFragment();
- final Bundle bundle = new Bundle();
- bundle.putBoolean(modeKeyName, true);
- fragment.setArguments(bundle);
- return fragment;
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onCreate(final Bundle bundle) {
- super.onCreate(bundle);
- mListBinding.getData().init(getLoaderManager(), mListBinding);
- mAdapter = new ConversationListAdapter(getActivity(), null, this);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- Assert.notNull(mHost);
- setScrolledToNewestConversationIfNeeded();
-
- updateUi();
- }
-
- public void setScrolledToNewestConversationIfNeeded() {
- if (!mArchiveMode
- && !mForwardMessageMode
- && isScrolledToFirstConversation()
- && mHost.hasWindowFocus()) {
- mListBinding.getData().setScrolledToNewestConversation(true);
- }
- }
-
- private boolean isScrolledToFirstConversation() {
- int firstItemPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
- .findFirstCompletelyVisibleItemPosition();
- return firstItemPosition == 0;
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onDestroy() {
- super.onDestroy();
- mListBinding.unbind();
- mHost = null;
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.conversation_list_fragment,
- container, false);
- mRecyclerView = (RecyclerView) rootView.findViewById(android.R.id.list);
- mEmptyListMessageView = (ListEmptyView) rootView.findViewById(R.id.no_conversations_view);
- mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list);
- // The default behavior for default layout param generation by LinearLayoutManager is to
- // provide width and height of WRAP_CONTENT, but this is not desirable for
- // ConversationListFragment; the view in each row should be a width of MATCH_PARENT so that
- // the entire row is tappable.
- final Activity activity = getActivity();
- final LinearLayoutManager manager = new LinearLayoutManager(activity) {
- @Override
- public RecyclerView.LayoutParams generateDefaultLayoutParams() {
- return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- }
- };
- mRecyclerView.setLayoutManager(manager);
- mRecyclerView.setHasFixedSize(true);
- mRecyclerView.setAdapter(mAdapter);
- mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
- int mCurrentState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
-
- @Override
- public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
- if (mCurrentState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL
- || mCurrentState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
- ImeUtil.get().hideImeKeyboard(getActivity(), mRecyclerView);
- }
-
- if (isScrolledToFirstConversation()) {
- setScrolledToNewestConversationIfNeeded();
- } else {
- mListBinding.getData().setScrolledToNewestConversation(false);
- }
- }
-
- @Override
- public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) {
- mCurrentState = newState;
- }
- });
- mRecyclerView.addOnItemTouchListener(new ConversationListSwipeHelper(mRecyclerView));
-
- if (savedInstanceState != null) {
- mListState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY);
- }
-
- mStartNewConversationButton = (ImageView) rootView.findViewById(
- R.id.start_new_conversation_button);
- if (mArchiveMode) {
- mStartNewConversationButton.setVisibility(View.GONE);
- } else {
- mStartNewConversationButton.setVisibility(View.VISIBLE);
- mStartNewConversationButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View clickView) {
- mHost.onCreateConversationClick();
- }
- });
- }
- ViewCompat.setTransitionName(mStartNewConversationButton, BugleAnimationTags.TAG_FABICON);
-
- // The root view has a non-null background, which by default is deemed by the framework
- // to be a "transition group," where all child views are animated together during an
- // activity transition. However, we want each individual items in the recycler view to
- // show explode animation themselves, so we explicitly tag the root view to be a non-group.
- ViewGroupCompat.setTransitionGroup(rootView, false);
-
- setHasOptionsMenu(true);
- return rootView;
- }
-
- @Override
- public void onAttach(final Activity activity) {
- super.onAttach(activity);
- if (VERBOSE) {
- LogUtil.v(LogUtil.BUGLE_TAG, "Attaching List");
- }
- final Bundle arguments = getArguments();
- if (arguments != null) {
- mArchiveMode = arguments.getBoolean(BUNDLE_ARCHIVED_MODE, false);
- mForwardMessageMode = arguments.getBoolean(BUNDLE_FORWARD_MESSAGE_MODE, false);
- }
- mListBinding.bind(DataModel.get().createConversationListData(activity, this, mArchiveMode));
- }
-
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mListState != null) {
- outState.putParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY, mListState);
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
- mListBinding.getData().setScrolledToNewestConversation(false);
- }
-
- /**
- * Call this immediately after attaching the fragment
- */
- public void setHost(final ConversationListFragmentHost host) {
- Assert.isNull(mHost);
- mHost = host;
- }
-
- @Override
- public void onConversationListCursorUpdated(final ConversationListData data,
- final Cursor cursor) {
- mListBinding.ensureBound(data);
- final Cursor oldCursor = mAdapter.swapCursor(cursor);
- updateEmptyListUi(cursor == null || cursor.getCount() == 0);
- if (mListState != null && cursor != null && oldCursor == null) {
- mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
- }
- }
-
- @Override
- public void setBlockedParticipantsAvailable(final boolean blockedAvailable) {
- mBlockedAvailable = blockedAvailable;
- if (mShowBlockedMenuItem != null) {
- mShowBlockedMenuItem.setVisible(blockedAvailable);
- }
- }
-
- public void updateUi() {
- mAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void onPrepareOptionsMenu(final Menu menu) {
- super.onPrepareOptionsMenu(menu);
- final MenuItem startNewConversationMenuItem =
- menu.findItem(R.id.action_start_new_conversation);
- if (startNewConversationMenuItem != null) {
- // It is recommended for the Floating Action button functionality to be duplicated as a
- // menu
- AccessibilityManager accessibilityManager = (AccessibilityManager)
- getActivity().getSystemService(Context.ACCESSIBILITY_SERVICE);
- startNewConversationMenuItem.setVisible(accessibilityManager
- .isTouchExplorationEnabled());
- }
-
- final MenuItem archive = menu.findItem(R.id.action_show_archived);
- if (archive != null) {
- archive.setVisible(true);
- }
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- if (!isAdded()) {
- // Guard against being called before we're added to the activity
- return;
- }
-
- mShowBlockedMenuItem = menu.findItem(R.id.action_show_blocked_contacts);
- if (mShowBlockedMenuItem != null) {
- mShowBlockedMenuItem.setVisible(mBlockedAvailable);
- }
- }
-
- /**
- * {@inheritDoc} from ConversationListItemView.HostInterface
- */
- @Override
- public void onConversationClicked(final ConversationListItemData conversationListItemData,
- final boolean isLongClick, final ConversationListItemView conversationView) {
- final ConversationListData listData = mListBinding.getData();
- mHost.onConversationClick(listData, conversationListItemData, isLongClick,
- conversationView);
- }
-
- /**
- * {@inheritDoc} from ConversationListItemView.HostInterface
- */
- @Override
- public boolean isConversationSelected(final String conversationId) {
- return mHost.isConversationSelected(conversationId);
- }
-
- @Override
- public boolean isSwipeAnimatable() {
- return mHost.isSwipeAnimatable();
- }
-
- // Show and hide empty list UI as needed with appropriate text based on view specifics
- private void updateEmptyListUi(final boolean isEmpty) {
- if (isEmpty) {
- int emptyListText;
- if (!mListBinding.getData().getHasFirstSyncCompleted()) {
- emptyListText = R.string.conversation_list_first_sync_text;
- } else if (mArchiveMode) {
- emptyListText = R.string.archived_conversation_list_empty_text;
- } else {
- emptyListText = R.string.conversation_list_empty_text;
- }
- mEmptyListMessageView.setTextHint(emptyListText);
- mEmptyListMessageView.setVisibility(View.VISIBLE);
- mEmptyListMessageView.setIsImageVisible(true);
- mEmptyListMessageView.setIsVerticallyCentered(true);
- } else {
- mEmptyListMessageView.setVisibility(View.GONE);
- }
- }
-
- @Override
- public List<SnackBarInteraction> getSnackBarInteractions() {
- final List<SnackBarInteraction> interactions = new ArrayList<SnackBarInteraction>(1);
- final SnackBarInteraction fabInteraction =
- new SnackBarInteraction.BasicSnackBarInteraction(mStartNewConversationButton);
- interactions.add(fabInteraction);
- return interactions;
- }
-
- private ViewPropertyAnimator getNormalizedFabAnimator() {
- return mStartNewConversationButton.animate()
- .setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
- .setDuration(getActivity().getResources().getInteger(
- R.integer.fab_animation_duration_ms));
- }
-
- public ViewPropertyAnimator dismissFab() {
- // To prevent clicking while animating.
- mStartNewConversationButton.setEnabled(false);
- final MarginLayoutParams lp =
- (MarginLayoutParams) mStartNewConversationButton.getLayoutParams();
- final float fabWidthWithLeftRightMargin = mStartNewConversationButton.getWidth()
- + lp.leftMargin + lp.rightMargin;
- final int direction = AccessibilityUtil.isLayoutRtl(mStartNewConversationButton) ? -1 : 1;
- return getNormalizedFabAnimator().translationX(direction * fabWidthWithLeftRightMargin);
- }
-
- public ViewPropertyAnimator showFab() {
- return getNormalizedFabAnimator().translationX(0).withEndAction(new Runnable() {
- @Override
- public void run() {
- // Re-enable clicks after the animation.
- mStartNewConversationButton.setEnabled(true);
- }
- });
- }
-
- public View getHeroElementForTransition() {
- return mArchiveMode ? null : mStartNewConversationButton;
- }
-
- @VisibleForAnimation
- public RecyclerView getRecyclerView() {
- return mRecyclerView;
- }
-
- @Override
- public void startFullScreenPhotoViewer(
- final Uri initialPhoto, final Rect initialPhotoBounds, final Uri photosUri) {
- UIIntents.get().launchFullScreenPhotoViewer(
- getActivity(), initialPhoto, initialPhotoBounds, photosUri);
- }
-
- @Override
- public void startFullScreenVideoViewer(final Uri videoUri) {
- UIIntents.get().launchFullScreenVideoViewer(getActivity(), videoUri);
- }
-
- @Override
- public boolean isSelectionMode() {
- return mHost != null && mHost.isSelectionMode();
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
deleted file mode 100644
index 7525182..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
+++ /dev/null
@@ -1,643 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationlist;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLayoutChangeListener;
-import android.view.View.OnLongClickListener;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.annotation.VisibleForAnimation;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.AsyncImageView;
-import com.android.messaging.ui.AudioAttachmentView;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.ui.SnackBar;
-import com.android.messaging.ui.SnackBarInteraction;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.Typefaces;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-import java.util.List;
-
-/**
- * The view for a single entry in a conversation list.
- */
-public class ConversationListItemView extends FrameLayout implements OnClickListener,
- OnLongClickListener, OnLayoutChangeListener {
- static final int UNREAD_SNIPPET_LINE_COUNT = 3;
- static final int NO_UNREAD_SNIPPET_LINE_COUNT = 1;
- private int mListItemReadColor;
- private int mListItemUnreadColor;
- private Typeface mListItemReadTypeface;
- private Typeface mListItemUnreadTypeface;
- private static String sPlusOneString;
- private static String sPlusNString;
-
- public interface HostInterface {
- boolean isConversationSelected(final String conversationId);
- void onConversationClicked(final ConversationListItemData conversationListItemData,
- boolean isLongClick, final ConversationListItemView conversationView);
- boolean isSwipeAnimatable();
- List<SnackBarInteraction> getSnackBarInteractions();
- void startFullScreenPhotoViewer(final Uri initialPhoto, final Rect initialPhotoBounds,
- final Uri photosUri);
- void startFullScreenVideoViewer(final Uri videoUri);
- boolean isSelectionMode();
- }
-
- private final OnClickListener fullScreenPreviewClickListener = new OnClickListener() {
- @Override
- public void onClick(final View v) {
- final String previewType = mData.getShowDraft() ?
- mData.getDraftPreviewContentType() : mData.getPreviewContentType();
- Assert.isTrue(ContentType.isImageType(previewType) ||
- ContentType.isVideoType(previewType));
-
- final Uri previewUri = mData.getShowDraft() ?
- mData.getDraftPreviewUri() : mData.getPreviewUri();
- if (ContentType.isImageType(previewType)) {
- final Uri imagesUri = mData.getShowDraft() ?
- MessagingContentProvider.buildDraftImagesUri(mData.getConversationId()) :
- MessagingContentProvider
- .buildConversationImagesUri(mData.getConversationId());
- final Rect previewImageBounds = UiUtils.getMeasuredBoundsOnScreen(v);
- mHostInterface.startFullScreenPhotoViewer(
- previewUri, previewImageBounds, imagesUri);
- } else {
- mHostInterface.startFullScreenVideoViewer(previewUri);
- }
- }
- };
-
- private final ConversationListItemData mData;
-
- private int mAnimatingCount;
- private ViewGroup mSwipeableContainer;
- private ViewGroup mCrossSwipeBackground;
- private ViewGroup mSwipeableContent;
- private TextView mConversationNameView;
- private TextView mSnippetTextView;
- private TextView mSubjectTextView;
- private TextView mTimestampTextView;
- private ContactIconView mContactIconView;
- private ImageView mContactCheckmarkView;
- private ImageView mNotificationBellView;
- private ImageView mFailedStatusIconView;
- private ImageView mCrossSwipeArchiveLeftImageView;
- private ImageView mCrossSwipeArchiveRightImageView;
- private AsyncImageView mImagePreviewView;
- private AudioAttachmentView mAudioAttachmentView;
- private HostInterface mHostInterface;
-
- public ConversationListItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = new ConversationListItemData();
- final Resources res = context.getResources();
- }
-
- @Override
- protected void onFinishInflate() {
- mSwipeableContainer = (ViewGroup) findViewById(R.id.swipeableContainer);
- mCrossSwipeBackground = (ViewGroup) findViewById(R.id.crossSwipeBackground);
- mSwipeableContent = (ViewGroup) findViewById(R.id.swipeableContent);
- mConversationNameView = (TextView) findViewById(R.id.conversation_name);
- mSnippetTextView = (TextView) findViewById(R.id.conversation_snippet);
- mSubjectTextView = (TextView) findViewById(R.id.conversation_subject);
- mTimestampTextView = (TextView) findViewById(R.id.conversation_timestamp);
- mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon);
- mContactCheckmarkView = (ImageView) findViewById(R.id.conversation_checkmark);
- mNotificationBellView = (ImageView) findViewById(R.id.conversation_notification_bell);
- mFailedStatusIconView = (ImageView) findViewById(R.id.conversation_failed_status_icon);
- mCrossSwipeArchiveLeftImageView = (ImageView) findViewById(R.id.crossSwipeArchiveIconLeft);
- mCrossSwipeArchiveRightImageView =
- (ImageView) findViewById(R.id.crossSwipeArchiveIconRight);
- mImagePreviewView = (AsyncImageView) findViewById(R.id.conversation_image_preview);
- mAudioAttachmentView = (AudioAttachmentView) findViewById(R.id.audio_attachment_view);
- mConversationNameView.addOnLayoutChangeListener(this);
- mSnippetTextView.addOnLayoutChangeListener(this);
-
- final Resources resources = getContext().getResources();
- mListItemReadColor = resources.getColor(R.color.conversation_list_item_read);
- mListItemUnreadColor = resources.getColor(R.color.conversation_list_item_unread);
-
- mListItemReadTypeface = Typefaces.getRobotoNormal();
- mListItemUnreadTypeface = Typefaces.getRobotoBold();
-
- if (OsUtil.isAtLeastL()) {
- setTransitionGroup(true);
- }
- }
-
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- if (v == mConversationNameView) {
- setConversationName();
- } else if (v == mSnippetTextView) {
- setSnippet();
- } else if (v == mSubjectTextView) {
- setSubject();
- }
- }
-
- private void setConversationName() {
- if (mData.getIsRead() || mData.getShowDraft()) {
- mConversationNameView.setTextColor(mListItemReadColor);
- mConversationNameView.setTypeface(mListItemReadTypeface);
- } else {
- mConversationNameView.setTextColor(mListItemUnreadColor);
- mConversationNameView.setTypeface(mListItemUnreadTypeface);
- }
-
- final String conversationName = mData.getName();
-
- // For group conversations, ellipsize the group members that do not fit
- final CharSequence ellipsizedName = UiUtils.commaEllipsize(
- conversationName,
- mConversationNameView.getPaint(),
- mConversationNameView.getMeasuredWidth(),
- getPlusOneString(),
- getPlusNString());
- // RTL : To format conversation name if it happens to be phone number.
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String bidiFormattedName = bidiFormatter.unicodeWrap(
- ellipsizedName.toString(),
- TextDirectionHeuristicsCompat.LTR);
-
- mConversationNameView.setText(bidiFormattedName);
- }
-
- private static String getPlusOneString() {
- if (sPlusOneString == null) {
- sPlusOneString = Factory.get().getApplicationContext().getResources()
- .getString(R.string.plus_one);
- }
- return sPlusOneString;
- }
-
- private static String getPlusNString() {
- if (sPlusNString == null) {
- sPlusNString = Factory.get().getApplicationContext().getResources()
- .getString(R.string.plus_n);
- }
- return sPlusNString;
- }
-
- private void setSubject() {
- final String subjectText = mData.getShowDraft() ?
- mData.getDraftSubject() :
- MmsUtils.cleanseMmsSubject(getContext().getResources(), mData.getSubject());
- if (!TextUtils.isEmpty(subjectText)) {
- final String subjectPrepend = getResources().getString(R.string.subject_label);
- mSubjectTextView.setText(TextUtils.concat(subjectPrepend, subjectText));
- mSubjectTextView.setVisibility(VISIBLE);
- } else {
- mSubjectTextView.setVisibility(GONE);
- }
- }
-
- private void setSnippet() {
- mSnippetTextView.setText(getSnippetText());
- }
-
- // Resource Ids of content descriptions prefixes for different message status.
- private static final int [][][] sPrimaryContentDescriptions = {
- // 1:1 conversation
- {
- // Incoming message
- {
- R.string.one_on_one_incoming_failed_message_prefix,
- R.string.one_on_one_incoming_successful_message_prefix
- },
- // Outgoing message
- {
- R.string.one_on_one_outgoing_failed_message_prefix,
- R.string.one_on_one_outgoing_successful_message_prefix,
- R.string.one_on_one_outgoing_draft_message_prefix,
- R.string.one_on_one_outgoing_sending_message_prefix,
- }
- },
-
- // Group conversation
- {
- // Incoming message
- {
- R.string.group_incoming_failed_message_prefix,
- R.string.group_incoming_successful_message_prefix,
- },
- // Outgoing message
- {
- R.string.group_outgoing_failed_message_prefix,
- R.string.group_outgoing_successful_message_prefix,
- R.string.group_outgoing_draft_message_prefix,
- R.string.group_outgoing_sending_message_prefix,
- }
- }
- };
-
- // Resource Id of the secondary part of the content description for an edge case of a message
- // which is in both draft status and failed status.
- private static final int sSecondaryContentDescription =
- R.string.failed_message_content_description;
-
- // 1:1 versus group
- private static final int CONV_TYPE_ONE_ON_ONE_INDEX = 0;
- private static final int CONV_TYPE_ONE_GROUP_INDEX = 1;
- // Direction
- private static final int DIRECTION_INCOMING_INDEX = 0;
- private static final int DIRECTION_OUTGOING_INDEX = 1;
- // Message status
- private static final int MESSAGE_STATUS_FAILED_INDEX = 0;
- private static final int MESSAGE_STATUS_SUCCESSFUL_INDEX = 1;
- private static final int MESSAGE_STATUS_DRAFT_INDEX = 2;
- private static final int MESSAGE_STATUS_SENDING_INDEX = 3;
-
- private static final int WIDTH_FOR_ACCESSIBLE_CONVERSATION_NAME = 600;
-
- public static String buildContentDescription(final Resources resources,
- final ConversationListItemData data, final TextPaint conversationNameViewPaint) {
- int messageStatusIndex;
- boolean outgoingSnippet = data.getIsMessageTypeOutgoing() || data.getShowDraft();
- if (outgoingSnippet) {
- if (data.getShowDraft()) {
- messageStatusIndex = MESSAGE_STATUS_DRAFT_INDEX;
- } else if (data.getIsSendRequested()) {
- messageStatusIndex = MESSAGE_STATUS_SENDING_INDEX;
- } else {
- messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX
- : MESSAGE_STATUS_SUCCESSFUL_INDEX;
- }
- } else {
- messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX
- : MESSAGE_STATUS_SUCCESSFUL_INDEX;
- }
-
- int resId = sPrimaryContentDescriptions
- [data.getIsGroup() ? CONV_TYPE_ONE_GROUP_INDEX : CONV_TYPE_ONE_ON_ONE_INDEX]
- [outgoingSnippet ? DIRECTION_OUTGOING_INDEX : DIRECTION_INCOMING_INDEX]
- [messageStatusIndex];
-
- final String snippetText = data.getShowDraft() ?
- data.getDraftSnippetText() : data.getSnippetText();
-
- final String conversationName = data.getName();
- String senderOrConvName = outgoingSnippet ? conversationName : data.getSnippetSenderName();
-
- String primaryContentDescription = resources.getString(resId, senderOrConvName,
- snippetText == null ? "" : snippetText,
- data.getFormattedTimestamp(),
- // This is used only for incoming group messages
- conversationName);
- String contentDescription = primaryContentDescription;
-
- // An edge case : for an outgoing message, it might be in both draft status and
- // failed status.
- if (outgoingSnippet && data.getShowDraft() && data.getIsFailedStatus()) {
- StringBuilder contentDescriptionBuilder = new StringBuilder();
- contentDescriptionBuilder.append(primaryContentDescription);
-
- String secondaryContentDescription =
- resources.getString(sSecondaryContentDescription);
- contentDescriptionBuilder.append(" ");
- contentDescriptionBuilder.append(secondaryContentDescription);
- contentDescription = contentDescriptionBuilder.toString();
- }
- return contentDescription;
- }
-
- /**
- * Fills in the data associated with this view.
- *
- * @param cursor The cursor from a ConversationList that this view is in, pointing to its
- * entry.
- */
- public void bind(final Cursor cursor, final HostInterface hostInterface) {
- // Update our UI model
- mHostInterface = hostInterface;
- mData.bind(cursor);
-
- resetAnimatingState();
-
- mSwipeableContainer.setOnClickListener(this);
- mSwipeableContainer.setOnLongClickListener(this);
-
- final Resources resources = getContext().getResources();
-
- int color;
- final int maxLines;
- final Typeface typeface;
- final int typefaceStyle = mData.getShowDraft() ? Typeface.ITALIC : Typeface.NORMAL;
- final String snippetText = getSnippetText();
-
- if (mData.getIsRead() || mData.getShowDraft()) {
- maxLines = TextUtils.isEmpty(snippetText) ? 0 : NO_UNREAD_SNIPPET_LINE_COUNT;
- color = mListItemReadColor;
- typeface = mListItemReadTypeface;
- } else {
- maxLines = TextUtils.isEmpty(snippetText) ? 0 : UNREAD_SNIPPET_LINE_COUNT;
- color = mListItemUnreadColor;
- typeface = mListItemUnreadTypeface;
- }
-
- mSnippetTextView.setMaxLines(maxLines);
- mSnippetTextView.setTextColor(color);
- mSnippetTextView.setTypeface(typeface, typefaceStyle);
- mSubjectTextView.setTextColor(color);
- mSubjectTextView.setTypeface(typeface, typefaceStyle);
-
- setSnippet();
- setConversationName();
- setSubject();
- setContentDescription(buildContentDescription(resources, mData,
- mConversationNameView.getPaint()));
-
- final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
- // don't show the error state unless we're the default sms app
- if (mData.getIsFailedStatus() && isDefaultSmsApp) {
- mTimestampTextView.setTextColor(resources.getColor(R.color.conversation_list_error));
- mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
- int failureMessageId = R.string.message_status_download_failed;
- if (mData.getIsMessageTypeOutgoing()) {
- failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(mData.getMessageStatus(),
- mData.getMessageRawTelephonyStatus());
- }
- mTimestampTextView.setText(resources.getString(failureMessageId));
- } else if (mData.getShowDraft()
- || mData.getMessageStatus() == MessageData.BUGLE_STATUS_OUTGOING_DRAFT
- // also check for unknown status which we get because sometimes the conversation
- // row is left with a latest_message_id of a no longer existing message and
- // therefore the join values come back as null (or in this case zero).
- || mData.getMessageStatus() == MessageData.BUGLE_STATUS_UNKNOWN) {
- mTimestampTextView.setTextColor(mListItemReadColor);
- mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
- mTimestampTextView.setText(resources.getString(
- R.string.conversation_list_item_view_draft_message));
- } else {
- mTimestampTextView.setTextColor(mListItemReadColor);
- mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
- final String formattedTimestamp = mData.getFormattedTimestamp();
- if (mData.getIsSendRequested()) {
- mTimestampTextView.setText(R.string.message_status_sending);
- } else {
- mTimestampTextView.setText(formattedTimestamp);
- }
- }
-
- final boolean isSelected = mHostInterface.isConversationSelected(mData.getConversationId());
- setSelected(isSelected);
- Uri iconUri = null;
- int contactIconVisibility = GONE;
- int checkmarkVisiblity = GONE;
- int failStatusVisiblity = GONE;
- if (isSelected) {
- checkmarkVisiblity = VISIBLE;
- } else {
- contactIconVisibility = VISIBLE;
- // Only show the fail icon if it is not a group conversation.
- // And also require that we be the default sms app.
- if (mData.getIsFailedStatus() && !mData.getIsGroup() && isDefaultSmsApp) {
- failStatusVisiblity = VISIBLE;
- }
- }
- if (mData.getIcon() != null) {
- iconUri = Uri.parse(mData.getIcon());
- }
- mContactIconView.setImageResourceUri(iconUri, mData.getParticipantContactId(),
- mData.getParticipantLookupKey(), mData.getOtherParticipantNormalizedDestination());
- mContactIconView.setVisibility(contactIconVisibility);
- mContactIconView.setOnLongClickListener(this);
- mContactIconView.setClickable(!mHostInterface.isSelectionMode());
- mContactIconView.setLongClickable(!mHostInterface.isSelectionMode());
-
- mContactCheckmarkView.setVisibility(checkmarkVisiblity);
- mFailedStatusIconView.setVisibility(failStatusVisiblity);
-
- final Uri previewUri = mData.getShowDraft() ?
- mData.getDraftPreviewUri() : mData.getPreviewUri();
- final String previewContentType = mData.getShowDraft() ?
- mData.getDraftPreviewContentType() : mData.getPreviewContentType();
- OnClickListener previewClickListener = null;
- Uri previewImageUri = null;
- int previewImageVisibility = GONE;
- int audioPreviewVisiblity = GONE;
- if (previewUri != null && !TextUtils.isEmpty(previewContentType)) {
- if (ContentType.isAudioType(previewContentType)) {
- mAudioAttachmentView.bind(previewUri, false);
- audioPreviewVisiblity = VISIBLE;
- } else if (ContentType.isVideoType(previewContentType)) {
- previewImageUri = UriUtil.getUriForResourceId(
- getContext(), R.drawable.ic_preview_play);
- previewClickListener = fullScreenPreviewClickListener;
- previewImageVisibility = VISIBLE;
- } else if (ContentType.isImageType(previewContentType)) {
- previewImageUri = previewUri;
- previewClickListener = fullScreenPreviewClickListener;
- previewImageVisibility = VISIBLE;
- }
- }
-
- final int imageSize = resources.getDimensionPixelSize(
- R.dimen.conversation_list_image_preview_size);
- mImagePreviewView.setImageResourceId(
- new UriImageRequestDescriptor(previewImageUri, imageSize, imageSize,
- true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */));
- mImagePreviewView.setOnLongClickListener(this);
- mImagePreviewView.setVisibility(previewImageVisibility);
- mImagePreviewView.setOnClickListener(previewClickListener);
- mAudioAttachmentView.setOnLongClickListener(this);
- mAudioAttachmentView.setVisibility(audioPreviewVisiblity);
-
- final int notificationBellVisiblity = mData.getNotificationEnabled() ? GONE : VISIBLE;
- mNotificationBellView.setVisibility(notificationBellVisiblity);
- }
-
- public boolean isSwipeAnimatable() {
- return mHostInterface.isSwipeAnimatable();
- }
-
- @VisibleForAnimation
- public float getSwipeTranslationX() {
- return mSwipeableContainer.getTranslationX();
- }
-
- @VisibleForAnimation
- public void setSwipeTranslationX(final float translationX) {
- mSwipeableContainer.setTranslationX(translationX);
- if (translationX == 0) {
- mCrossSwipeBackground.setVisibility(View.GONE);
- mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
- mCrossSwipeArchiveRightImageView.setVisibility(GONE);
-
- mSwipeableContainer.setBackgroundColor(Color.TRANSPARENT);
- } else {
- mCrossSwipeBackground.setVisibility(View.VISIBLE);
- if (translationX > 0) {
- mCrossSwipeArchiveLeftImageView.setVisibility(VISIBLE);
- mCrossSwipeArchiveRightImageView.setVisibility(GONE);
- } else {
- mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
- mCrossSwipeArchiveRightImageView.setVisibility(VISIBLE);
- }
- mSwipeableContainer.setBackgroundResource(R.drawable.swipe_shadow_drag);
- }
- }
-
- public void onSwipeComplete() {
- final String conversationId = mData.getConversationId();
- UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
-
- final Runnable undoRunnable = new Runnable() {
- @Override
- public void run() {
- UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
- }
- };
- final String message = getResources().getString(R.string.archived_toast_message, 1);
- UiUtils.showSnackBar(getContext(), getRootView(), message, undoRunnable,
- SnackBar.Action.SNACK_BAR_UNDO,
- mHostInterface.getSnackBarInteractions());
- }
-
- private void setShortAndLongClickable(final boolean clickable) {
- setClickable(clickable);
- setLongClickable(clickable);
- }
-
- private void resetAnimatingState() {
- mAnimatingCount = 0;
- setShortAndLongClickable(true);
- setSwipeTranslationX(0);
- }
-
- /**
- * Notifies this view that it is undergoing animation. This view should disable its click
- * targets.
- *
- * The animating counter is used to reset the swipe controller when the counter becomes 0. A
- * positive counter also makes the view not clickable.
- */
- public final void setAnimating(final boolean animating) {
- final int oldAnimatingCount = mAnimatingCount;
- if (animating) {
- mAnimatingCount++;
- } else {
- mAnimatingCount--;
- if (mAnimatingCount < 0) {
- mAnimatingCount = 0;
- }
- }
-
- if (mAnimatingCount == 0) {
- // New count is 0. All animations ended.
- setShortAndLongClickable(true);
- } else if (oldAnimatingCount == 0) {
- // New count is > 0. Waiting for some animations to end.
- setShortAndLongClickable(false);
- }
- }
-
- public boolean isAnimating() {
- return mAnimatingCount > 0;
- }
-
- /**
- * {@inheritDoc} from OnClickListener
- */
- @Override
- public void onClick(final View v) {
- processClick(v, false);
- }
-
- /**
- * {@inheritDoc} from OnLongClickListener
- */
- @Override
- public boolean onLongClick(final View v) {
- return processClick(v, true);
- }
-
- private boolean processClick(final View v, final boolean isLongClick) {
- Assert.isTrue(v == mSwipeableContainer || v == mContactIconView || v == mImagePreviewView);
- Assert.notNull(mData.getName());
-
- if (mHostInterface != null) {
- mHostInterface.onConversationClicked(mData, isLongClick, this);
- return true;
- }
- return false;
- }
-
- public View getSwipeableContent() {
- return mSwipeableContent;
- }
-
- public View getContactIconView() {
- return mContactIconView;
- }
-
- private String getSnippetText() {
- String snippetText = mData.getShowDraft() ?
- mData.getDraftSnippetText() : mData.getSnippetText();
- final String previewContentType = mData.getShowDraft() ?
- mData.getDraftPreviewContentType() : mData.getPreviewContentType();
- if (TextUtils.isEmpty(snippetText)) {
- Resources resources = getResources();
- // Use the attachment type as a snippet so the preview doesn't look odd
- if (ContentType.isAudioType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_audio_clip);
- } else if (ContentType.isImageType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_picture);
- } else if (ContentType.isVideoType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_video);
- } else if (ContentType.isVCardType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_vcard);
- }
- }
- return snippetText;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java b/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
deleted file mode 100644
index 4988259..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationlist;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.OnItemTouchListener;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Animation and touch helper class for Conversation List swipe.
- */
-public class ConversationListSwipeHelper implements OnItemTouchListener {
- private static final int UNIT_SECONDS = 1000;
- private static final boolean ANIMATING = true;
-
- private static final float ERROR_FACTOR_MULTIPLIER = 1.2f;
- private static final float PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.4f;
- private static final float FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.05f;
-
- private static final int SWIPE_DIRECTION_NONE = 0;
- private static final int SWIPE_DIRECTION_LEFT = 1;
- private static final int SWIPE_DIRECTION_RIGHT = 2;
-
- private final RecyclerView mRecyclerView;
- private final long mDefaultRestoreAnimationDuration;
- private final long mDefaultDismissAnimationDuration;
- private final long mMaxTranslationAnimationDuration;
- private final int mTouchSlop;
- private final int mMinimumFlingVelocity;
- private final int mMaximumFlingVelocity;
-
- /* Valid throughout a single gesture. */
- private VelocityTracker mVelocityTracker;
- private float mInitialX;
- private float mInitialY;
- private boolean mIsSwiping;
- private ConversationListItemView mListItemView;
-
- public ConversationListSwipeHelper(final RecyclerView recyclerView) {
- mRecyclerView = recyclerView;
-
- final Context context = mRecyclerView.getContext();
- final Resources res = context.getResources();
- mDefaultRestoreAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
- mDefaultDismissAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
- mMaxTranslationAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
-
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
- mTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
- mMaximumFlingVelocity = Math.min(
- viewConfiguration.getScaledMaximumFlingVelocity(),
- res.getInteger(R.integer.swipe_max_fling_velocity_px_per_s));
- mMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
- }
-
- @Override
- public boolean onInterceptTouchEvent(final RecyclerView recyclerView, final MotionEvent event) {
- if (event.getPointerCount() > 1) {
- // Ignore subsequent pointers.
- return false;
- }
-
- // We are not yet tracking a swipe gesture. Begin detection by spying on
- // touch events bubbling down to our children.
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- if (!hasGestureSwipeTarget()) {
- onGestureStart();
-
- mVelocityTracker.addMovement(event);
- mInitialX = event.getX();
- mInitialY = event.getY();
-
- final View viewAtPoint = mRecyclerView.findChildViewUnder(mInitialX, mInitialY);
- final ConversationListItemView child = (ConversationListItemView) viewAtPoint;
- if (viewAtPoint instanceof ConversationListItemView &&
- child != null && child.isSwipeAnimatable()) {
- // Begin detecting swipe on the target for the rest of the gesture.
- mListItemView = child;
- if (mListItemView.isAnimating()) {
- mListItemView = null;
- }
- } else {
- mListItemView = null;
- }
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (hasValidGestureSwipeTarget()) {
- mVelocityTracker.addMovement(event);
-
- final int historicalCount = event.getHistorySize();
- // First consume the historical events, then consume the current ones.
- for (int i = 0; i < historicalCount + 1; i++) {
- float currX;
- float currY;
- if (i < historicalCount) {
- currX = event.getHistoricalX(i);
- currY = event.getHistoricalY(i);
- } else {
- currX = event.getX();
- currY = event.getY();
- }
- final float deltaX = currX - mInitialX;
- final float deltaY = currY - mInitialY;
- final float absDeltaX = Math.abs(deltaX);
- final float absDeltaY = Math.abs(deltaY);
-
- if (!mIsSwiping && absDeltaY > mTouchSlop
- && absDeltaY > (ERROR_FACTOR_MULTIPLIER * absDeltaX)) {
- // Stop detecting swipe for the remainder of this gesture.
- onGestureEnd();
- return false;
- }
-
- if (absDeltaX > mTouchSlop) {
- // Swipe detected. Return true so we can handle the gesture in
- // onTouchEvent.
- mIsSwiping = true;
-
- // We don't want to suddenly jump the slop distance.
- mInitialX = event.getX();
- mInitialY = event.getY();
-
- onSwipeGestureStart(mListItemView);
- return true;
- }
- }
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (hasGestureSwipeTarget()) {
- onGestureEnd();
- }
- break;
- }
-
- // Start intercepting touch events from children if we detect a swipe.
- return mIsSwiping;
- }
-
- @Override
- public void onTouchEvent(final RecyclerView recyclerView, final MotionEvent event) {
- // We should only be here if we intercepted the touch due to swipe.
- Assert.isTrue(mIsSwiping);
-
- // We are now tracking a swipe gesture.
- mVelocityTracker.addMovement(event);
-
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_OUTSIDE:
- case MotionEvent.ACTION_MOVE:
- if (hasValidGestureSwipeTarget()) {
- mListItemView.setSwipeTranslationX(event.getX() - mInitialX);
- }
- break;
- case MotionEvent.ACTION_UP:
- if (hasValidGestureSwipeTarget()) {
- final float maxVelocity = mMaximumFlingVelocity;
- mVelocityTracker.computeCurrentVelocity(UNIT_SECONDS, maxVelocity);
- final float velocityX = getLastComputedXVelocity();
-
- final float translationX = mListItemView.getSwipeTranslationX();
-
- int swipeDirection = SWIPE_DIRECTION_NONE;
- if (translationX != 0) {
- swipeDirection =
- translationX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
- } else if (velocityX != 0) {
- swipeDirection =
- velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
- }
-
- final boolean fastEnough = isTargetSwipedFastEnough();
- final boolean farEnough = isTargetSwipedFarEnough();
-
- final boolean shouldDismiss = (fastEnough || farEnough);
-
- if (shouldDismiss) {
- if (fastEnough) {
- animateDismiss(mListItemView, velocityX);
- } else {
- animateDismiss(mListItemView, swipeDirection);
- }
- } else {
- animateRestore(mListItemView, velocityX);
- }
-
- onSwipeGestureEnd(mListItemView,
- shouldDismiss ? swipeDirection : SWIPE_DIRECTION_NONE);
- } else {
- onGestureEnd();
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- if (hasValidGestureSwipeTarget()) {
- animateRestore(mListItemView, 0f);
- onSwipeGestureEnd(mListItemView, SWIPE_DIRECTION_NONE);
- } else {
- onGestureEnd();
- }
- break;
- }
- }
-
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- }
-
- /**
- * We have started to intercept a series of touch events.
- */
- private void onGestureStart() {
- mIsSwiping = false;
- // Work around bug in RecyclerView that sends two identical ACTION_DOWN
- // events to #onInterceptTouchEvent.
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.clear();
- }
-
- /**
- * The series of touch events has been detected as a swipe.
- *
- * Now that the gesture is a swipe, we will begin translating the view of the
- * given viewHolder.
- */
- private void onSwipeGestureStart(final ConversationListItemView itemView) {
- mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
- setHardwareAnimatingLayerType(itemView, ANIMATING);
- itemView.setAnimating(true);
- }
-
- /**
- * The current swipe gesture is complete.
- */
- private void onSwipeGestureEnd(final ConversationListItemView itemView,
- final int swipeDirection) {
- if (swipeDirection == SWIPE_DIRECTION_RIGHT || swipeDirection == SWIPE_DIRECTION_LEFT) {
- itemView.onSwipeComplete();
- }
-
- // Balances out onSwipeGestureStart.
- itemView.setAnimating(false);
-
- onGestureEnd();
- }
-
- /**
- * The series of touch events has ended in an {@link MotionEvent#ACTION_UP}
- * or {@link MotionEvent#ACTION_CANCEL}.
- */
- private void onGestureEnd() {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- mIsSwiping = false;
- mListItemView = null;
- }
-
- /**
- * A swipe animation has started.
- */
- private void onSwipeAnimationStart(final ConversationListItemView itemView) {
- // Disallow interactions.
- itemView.setAnimating(true);
- ViewCompat.setHasTransientState(itemView, true);
- setHardwareAnimatingLayerType(itemView, ANIMATING);
- }
-
- /**
- * The swipe animation has ended.
- */
- private void onSwipeAnimationEnd(final ConversationListItemView itemView) {
- // Restore interactions.
- itemView.setAnimating(false);
- ViewCompat.setHasTransientState(itemView, false);
- setHardwareAnimatingLayerType(itemView, !ANIMATING);
- }
-
- /**
- * Animate the dismissal of the given item. The given velocityX is taken into consideration for
- * the animation duration. Whether the item is dismissed to the left or right is dependent on
- * the given velocityX.
- */
- private void animateDismiss(final ConversationListItemView itemView, final float velocityX) {
- Assert.isTrue(velocityX != 0);
- final int direction = velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
- animateDismiss(itemView, direction, velocityX);
- }
-
- /**
- * Animate the dismissal of the given item. The velocityX is assumed to be 0.
- */
- private void animateDismiss(final ConversationListItemView itemView, final int swipeDirection) {
- animateDismiss(itemView, swipeDirection, 0f);
- }
-
- /**
- * Animate the dismissal of the given item.
- */
- private void animateDismiss(final ConversationListItemView itemView,
- final int swipeDirection, final float velocityX) {
- Assert.isTrue(swipeDirection != SWIPE_DIRECTION_NONE);
-
- onSwipeAnimationStart(itemView);
-
- final float animateTo = (swipeDirection == SWIPE_DIRECTION_RIGHT) ?
- mRecyclerView.getWidth() : -mRecyclerView.getWidth();
- final long duration;
- if (velocityX != 0) {
- final float deltaX = animateTo - itemView.getSwipeTranslationX();
- duration = calculateTranslationDuration(deltaX, velocityX);
- } else {
- duration = mDefaultDismissAnimationDuration;
- }
-
- final ObjectAnimator animator = getSwipeTranslationXAnimator(
- itemView, animateTo, duration, UiUtils.DEFAULT_INTERPOLATOR);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- onSwipeAnimationEnd(itemView);
- }
- });
- animator.start();
- }
-
- /**
- * Animate the bounce back of the given item.
- */
- private void animateRestore(final ConversationListItemView itemView,
- final float velocityX) {
- onSwipeAnimationStart(itemView);
-
- final float translationX = itemView.getSwipeTranslationX();
- final long duration;
- if (velocityX != 0 // Has velocity.
- && velocityX > 0 != translationX > 0) { // Right direction.
- duration = calculateTranslationDuration(translationX, velocityX);
- } else {
- duration = mDefaultRestoreAnimationDuration;
- }
- final ObjectAnimator animator = getSwipeTranslationXAnimator(
- itemView, 0f, duration, UiUtils.DEFAULT_INTERPOLATOR);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- onSwipeAnimationEnd(itemView);
- }
- });
- animator.start();
- }
-
- /**
- * Create and start an animator that animates the given view's translationX
- * from its current value to the value given by animateTo.
- */
- private ObjectAnimator getSwipeTranslationXAnimator(final ConversationListItemView itemView,
- final float animateTo, final long duration, final TimeInterpolator interpolator) {
- final ObjectAnimator animator =
- ObjectAnimator.ofFloat(itemView, "swipeTranslationX", animateTo);
- animator.setDuration(duration);
- animator.setInterpolator(interpolator);
- return animator;
- }
-
- /**
- * Determine if the swipe has enough velocity to be dismissed.
- */
- private boolean isTargetSwipedFastEnough() {
- final float velocityX = getLastComputedXVelocity();
- final float velocityY = mVelocityTracker.getYVelocity();
- final float minVelocity = mMinimumFlingVelocity;
- final float translationX = mListItemView.getSwipeTranslationX();
- final float width = mListItemView.getWidth();
- return (Math.abs(velocityX) > minVelocity) // Fast enough.
- && (Math.abs(velocityX) > Math.abs(velocityY)) // Not unintentional.
- && (velocityX > 0) == (translationX > 0) // Right direction.
- && Math.abs(translationX) >
- FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement.
- }
-
- /**
- * Only used during a swipe gesture. Determine if the swipe has enough distance to be
- * dismissed.
- */
- private boolean isTargetSwipedFarEnough() {
- final float velocityX = getLastComputedXVelocity();
-
- final float translationX = mListItemView.getSwipeTranslationX();
- final float width = mListItemView.getWidth();
-
- return (velocityX >= 0) == (translationX > 0) // Right direction.
- && Math.abs(translationX) >
- PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement.
- }
-
- private long calculateTranslationDuration(final float deltaPosition, final float velocity) {
- Assert.isTrue(velocity != 0);
- final float durationInSeconds = Math.abs(deltaPosition / velocity);
- return Math.min((int) (durationInSeconds * UNIT_SECONDS), mMaxTranslationAnimationDuration);
- }
-
- private boolean hasGestureSwipeTarget() {
- return mListItemView != null;
- }
-
- private boolean hasValidGestureSwipeTarget() {
- return hasGestureSwipeTarget() && mListItemView.getParent() == mRecyclerView;
- }
-
- /**
- * Enable a hardware layer for the it view and build that layer.
- */
- private void setHardwareAnimatingLayerType(final ConversationListItemView itemView,
- final boolean animating) {
- if (animating) {
- itemView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- if (itemView.getWindowToken() != null) {
- itemView.buildLayer();
- }
- } else {
- itemView.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
- private float getLastComputedXVelocity() {
- return mVelocityTracker.getXVelocity();
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java b/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java
deleted file mode 100644
index 61e3640..0000000
--- a/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversationlist;
-
-import android.app.Fragment;
-import android.os.Bundle;
-
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.ui.BaseBugleActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
-import com.android.messaging.util.Assert;
-
-/**
- * An activity that lets the user forward a SMS/MMS message by picking from a conversation in the
- * conversation list.
- */
-public class ForwardMessageActivity extends BaseBugleActivity
- implements ConversationListFragmentHost {
- private MessageData mDraftMessage;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final ConversationListFragment fragment =
- ConversationListFragment.createForwardMessageConversationListFragment();
- getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit();
- mDraftMessage = getIntent().getParcelableExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- Assert.isTrue(fragment instanceof ConversationListFragment);
- final ConversationListFragment clf = (ConversationListFragment) fragment;
- clf.setHost(this);
- }
-
- @Override
- public void onConversationClick(final ConversationListData listData,
- final ConversationListItemData conversationListItemData,
- final boolean isLongClick, final ConversationListItemView converastionView) {
- UIIntents.get().launchConversationActivity(
- this, conversationListItemData.getConversationId(), mDraftMessage);
- }
-
- @Override
- public void onCreateConversationClick() {
- UIIntents.get().launchCreateNewConversationActivity(this, mDraftMessage);
- }
-
- @Override
- public boolean isConversationSelected(final String conversationId) {
- return false;
- }
-
- @Override
- public boolean isSwipeAnimatable() {
- return false;
- }
-
- @Override
- public boolean isSelectionMode() {
- return false;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java b/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java
deleted file mode 100644
index bfeec51..0000000
--- a/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationlist;
-
-import android.support.v4.util.ArrayMap;
-import android.text.TextUtils;
-import android.view.ActionMode;
-import android.view.ActionMode.Callback;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.util.Assert;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-public class MultiSelectActionModeCallback implements Callback {
- private HashSet<String> mBlockedSet;
-
- public interface Listener {
- void onActionBarDelete(Collection<SelectedConversation> conversations);
- void onActionBarArchive(Iterable<SelectedConversation> conversations,
- boolean isToArchive);
- void onActionBarNotification(Iterable<SelectedConversation> conversations,
- boolean isNotificationOn);
- void onActionBarAddContact(final SelectedConversation conversation);
- void onActionBarBlock(final SelectedConversation conversation);
- void onActionBarHome();
- }
-
- static class SelectedConversation {
- public final String conversationId;
- public final long timestamp;
- public final String icon;
- public final String otherParticipantNormalizedDestination;
- public final CharSequence participantLookupKey;
- public final boolean isGroup;
- public final boolean isArchived;
- public final boolean notificationEnabled;
- public SelectedConversation(ConversationListItemData data) {
- conversationId = data.getConversationId();
- timestamp = data.getTimestamp();
- icon = data.getIcon();
- otherParticipantNormalizedDestination = data.getOtherParticipantNormalizedDestination();
- participantLookupKey = data.getParticipantLookupKey();
- isGroup = data.getIsGroup();
- isArchived = data.getIsArchived();
- notificationEnabled = data.getNotificationEnabled();
- }
- }
-
- private final ArrayMap<String, SelectedConversation> mSelectedConversations;
-
- private Listener mListener;
- private MenuItem mArchiveMenuItem;
- private MenuItem mUnarchiveMenuItem;
- private MenuItem mAddContactMenuItem;
- private MenuItem mBlockMenuItem;
- private MenuItem mNotificationOnMenuItem;
- private MenuItem mNotificationOffMenuItem;
- private boolean mHasInflated;
-
- public MultiSelectActionModeCallback(final Listener listener) {
- mListener = listener;
- mSelectedConversations = new ArrayMap<>();
-
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
- actionMode.getMenuInflater().inflate(R.menu.conversation_list_fragment_select_menu, menu);
- mArchiveMenuItem = menu.findItem(R.id.action_archive);
- mUnarchiveMenuItem = menu.findItem(R.id.action_unarchive);
- mAddContactMenuItem = menu.findItem(R.id.action_add_contact);
- mBlockMenuItem = menu.findItem(R.id.action_block);
- mNotificationOffMenuItem = menu.findItem(R.id.action_notification_off);
- mNotificationOnMenuItem = menu.findItem(R.id.action_notification_on);
- mHasInflated = true;
- updateActionIconsVisiblity();
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
- switch(menuItem.getItemId()) {
- case R.id.action_delete:
- mListener.onActionBarDelete(mSelectedConversations.values());
- return true;
- case R.id.action_archive:
- mListener.onActionBarArchive(mSelectedConversations.values(), true);
- return true;
- case R.id.action_unarchive:
- mListener.onActionBarArchive(mSelectedConversations.values(), false);
- return true;
- case R.id.action_notification_off:
- mListener.onActionBarNotification(mSelectedConversations.values(), false);
- return true;
- case R.id.action_notification_on:
- mListener.onActionBarNotification(mSelectedConversations.values(), true);
- return true;
- case R.id.action_add_contact:
- Assert.isTrue(mSelectedConversations.size() == 1);
- mListener.onActionBarAddContact(mSelectedConversations.valueAt(0));
- return true;
- case R.id.action_block:
- Assert.isTrue(mSelectedConversations.size() == 1);
- mListener.onActionBarBlock(mSelectedConversations.valueAt(0));
- return true;
- case android.R.id.home:
- mListener.onActionBarHome();
- return true;
- default:
- return false;
- }
- }
-
- @Override
- public void onDestroyActionMode(ActionMode actionMode) {
- mListener = null;
- mSelectedConversations.clear();
- mHasInflated = false;
- }
-
- public void toggleSelect(final ConversationListData listData,
- final ConversationListItemData conversationListItemData) {
- Assert.notNull(conversationListItemData);
- mBlockedSet = listData.getBlockedParticipants();
- final String id = conversationListItemData.getConversationId();
- if (mSelectedConversations.containsKey(id)) {
- mSelectedConversations.remove(id);
- } else {
- mSelectedConversations.put(id, new SelectedConversation(conversationListItemData));
- }
-
- if (mSelectedConversations.isEmpty()) {
- mListener.onActionBarHome();
- } else {
- updateActionIconsVisiblity();
- }
- }
-
- public boolean isSelected(final String selectedId) {
- return mSelectedConversations.containsKey(selectedId);
- }
-
- private void updateActionIconsVisiblity() {
- if (!mHasInflated) {
- return;
- }
-
- if (mSelectedConversations.size() == 1) {
- final SelectedConversation conversation = mSelectedConversations.valueAt(0);
- // The look up key is a key given to us by contacts app, so if we have a look up key,
- // we know that the participant is already in contacts.
- final boolean isInContacts = !TextUtils.isEmpty(conversation.participantLookupKey);
- mAddContactMenuItem.setVisible(!conversation.isGroup && !isInContacts);
- // ParticipantNormalizedDestination is always null for group conversations.
- final String otherParticipant = conversation.otherParticipantNormalizedDestination;
- mBlockMenuItem.setVisible(otherParticipant != null
- && !mBlockedSet.contains(otherParticipant));
- } else {
- mBlockMenuItem.setVisible(false);
- mAddContactMenuItem.setVisible(false);
- }
-
- boolean hasCurrentlyArchived = false;
- boolean hasCurrentlyUnarchived = false;
- boolean hasCurrentlyOnNotification = false;
- boolean hasCurrentlyOffNotification = false;
- final Iterable<SelectedConversation> conversations = mSelectedConversations.values();
- for (final SelectedConversation conversation : conversations) {
- if (conversation.notificationEnabled) {
- hasCurrentlyOnNotification = true;
- } else {
- hasCurrentlyOffNotification = true;
- }
-
- if (conversation.isArchived) {
- hasCurrentlyArchived = true;
- } else {
- hasCurrentlyUnarchived = true;
- }
-
- // If we found at least one of each example we don't need to keep looping.
- if (hasCurrentlyOffNotification && hasCurrentlyOnNotification &&
- hasCurrentlyArchived && hasCurrentlyUnarchived) {
- break;
- }
- }
- // If we have notification off conversations we show on button, if we have notification on
- // conversation we show off button. We can show both if we have a mixture.
- mNotificationOffMenuItem.setVisible(hasCurrentlyOnNotification);
- mNotificationOnMenuItem.setVisible(hasCurrentlyOffNotification);
-
- mArchiveMenuItem.setVisible(hasCurrentlyUnarchived);
- mUnarchiveMenuItem.setVisible(hasCurrentlyArchived);
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
deleted file mode 100644
index ef7fcef..0000000
--- a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversationlist;
-
-import android.app.Fragment;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.ui.BaseBugleActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaMetadataRetrieverWrapper;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-public class ShareIntentActivity extends BaseBugleActivity implements
- ShareIntentFragment.HostInterface {
-
- private MessageData mDraftMessage;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Intent intent = getIntent();
- if (Intent.ACTION_SEND.equals(intent.getAction()) &&
- (!TextUtils.isEmpty(intent.getStringExtra("address")) ||
- !TextUtils.isEmpty(intent.getStringExtra(Intent.EXTRA_EMAIL)))) {
- // This is really more like a SENDTO intent because a destination is supplied.
- // It's coming through the SEND intent because that's the intent that is used
- // when invoking the chooser with Intent.createChooser().
- final Intent convIntent = UIIntents.get().getLaunchConversationActivityIntent(this);
- // Copy the important items from the original intent to the new intent.
- convIntent.putExtras(intent);
- convIntent.setAction(Intent.ACTION_SENDTO);
- convIntent.setDataAndType(intent.getData(), intent.getType());
- // We have to fire off the intent and finish before trying to show the fragment,
- // otherwise we get some flashing.
- startActivity(convIntent);
- finish();
- return;
- }
- new ShareIntentFragment().show(getFragmentManager(), "ShareIntentFragment");
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- final Intent intent = getIntent();
- final String action = intent.getAction();
- if (Intent.ACTION_SEND.equals(action)) {
- final Uri contentUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
- final String contentType = extractContentType(contentUri, intent.getType());
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, String.format(
- "onAttachFragment: contentUri=%s, intent.getType()=%s, inferredType=%s",
- contentUri, intent.getType(), contentType));
- }
- if (ContentType.TEXT_PLAIN.equals(contentType)) {
- final String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
- if (sharedText != null) {
- mDraftMessage = MessageData.createSharedMessage(sharedText);
- } else {
- mDraftMessage = null;
- }
- } else if (ContentType.isImageType(contentType) ||
- ContentType.isVCardType(contentType) ||
- ContentType.isAudioType(contentType) ||
- ContentType.isVideoType(contentType)) {
- if (contentUri != null) {
- mDraftMessage = MessageData.createSharedMessage(null);
- addSharedImagePartToDraft(contentType, contentUri);
- } else {
- mDraftMessage = null;
- }
- } else {
- // Unsupported content type.
- Assert.fail("Unsupported shared content type for " + contentUri + ": " + contentType
- + " (" + intent.getType() + ")");
- }
- } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
- final String contentType = intent.getType();
- if (ContentType.isImageType(contentType)) {
- // Handle sharing multiple images.
- final ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(
- Intent.EXTRA_STREAM);
- if (imageUris != null && imageUris.size() > 0) {
- mDraftMessage = MessageData.createSharedMessage(null);
- for (final Uri imageUri : imageUris) {
- final String actualContentType = extractContentType(imageUri, contentType);
- addSharedImagePartToDraft(actualContentType, imageUri);
- }
- } else {
- mDraftMessage = null;
- }
- } else {
- // Unsupported content type.
- Assert.fail("Unsupported shared content type: " + contentType);
- }
- } else {
- // Unsupported action.
- Assert.fail("Unsupported action type for sharing: " + action);
- }
- }
-
- private static String extractContentType(final Uri uri, final String contentType) {
- if (uri == null) {
- return contentType;
- }
- // First try looking at file extension. This is less reliable in some ways but it's
- // recommended by
- // https://developer.android.com/training/secure-file-sharing/retrieve-info.html
- // Some implementations of MediaMetadataRetriever get things horribly
- // wrong for common formats such as jpeg (reports as video/ffmpeg)
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final String typeFromExtension = resolver.getType(uri);
- if (typeFromExtension != null) {
- return typeFromExtension;
- }
- final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
- try {
- retriever.setDataSource(uri);
- final String extractedType = retriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_MIMETYPE);
- if (extractedType != null) {
- return extractedType;
- }
- } catch (final IOException e) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Could not determine type of " + uri, e);
- } finally {
- retriever.release();
- }
- return contentType;
- }
-
- private void addSharedImagePartToDraft(final String contentType, final Uri imageUri) {
- mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType,
- imageUri));
- }
-
- @Override
- public void onConversationClick(final ConversationListItemData conversationListItemData) {
- UIIntents.get().launchConversationActivity(
- this, conversationListItemData.getConversationId(), mDraftMessage);
- finish();
- }
-
- @Override
- public void onCreateConversationClick() {
- UIIntents.get().launchCreateNewConversationActivity(this, mDraftMessage);
- finish();
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java b/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java
deleted file mode 100644
index e894145..0000000
--- a/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversationlist;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.ui.CursorRecyclerAdapter;
-import com.android.messaging.ui.PersonItemView;
-import com.android.messaging.ui.PersonItemView.PersonItemViewListener;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Turn conversation rows into PeopleItemViews
- */
-public class ShareIntentAdapter
- extends CursorRecyclerAdapter<ShareIntentAdapter.ShareIntentViewHolder> {
-
- public interface HostInterface {
- void onConversationClicked(final ConversationListItemData conversationListItemData);
- }
-
- private final HostInterface mHostInterface;
-
- public ShareIntentAdapter(final Context context, final Cursor cursor,
- final HostInterface hostInterface) {
- super(context, cursor, 0);
- mHostInterface = hostInterface;
- setHasStableIds(true);
- }
-
- @Override
- public void bindViewHolder(final ShareIntentViewHolder holder, final Context context,
- final Cursor cursor) {
- holder.bind(cursor);
- }
-
- @Override
- public ShareIntentViewHolder createViewHolder(final Context context,
- final ViewGroup parent, final int viewType) {
- final PersonItemView itemView = (PersonItemView) LayoutInflater.from(context).inflate(
- R.layout.people_list_item_view, null);
- return new ShareIntentViewHolder(itemView);
- }
-
- /**
- * Holds a PersonItemView and keeps it synced with a ConversationListItemData.
- */
- public class ShareIntentViewHolder extends RecyclerView.ViewHolder implements
- PersonItemView.PersonItemViewListener {
- private final ConversationListItemData mData = new ConversationListItemData();
- private final PersonItemData mItemData = new PersonItemData() {
- @Override
- public Uri getAvatarUri() {
- return mData.getIcon() == null ? null : Uri.parse(mData.getIcon());
- }
-
- @Override
- public String getDisplayName() {
- return mData.getName();
- }
-
- @Override
- public String getDetails() {
- final String conversationName = mData.getName();
- final String conversationPhone = PhoneUtils.getDefault().formatForDisplay(
- mData.getOtherParticipantNormalizedDestination());
- if (conversationPhone == null || conversationPhone.equals(conversationName)) {
- return null;
- }
- return conversationPhone;
- }
-
- @Override
- public Intent getClickIntent() {
- return null;
- }
-
- @Override
- public long getContactId() {
- return ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
- }
-
- @Override
- public String getLookupKey() {
- return null;
- }
-
- @Override
- public String getNormalizedDestination() {
- return null;
- }
- };
-
- public ShareIntentViewHolder(final PersonItemView itemView) {
- super(itemView);
- itemView.setListener(this);
- }
-
- public void bind(Cursor cursor) {
- mData.bind(cursor);
- ((PersonItemView) itemView).bind(mItemData);
- }
-
- @Override
- public void onPersonClicked(PersonItemData data) {
- mHostInterface.onConversationClicked(mData);
- }
-
- @Override
- public boolean onPersonLongClicked(PersonItemData data) {
- return false;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java b/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java
deleted file mode 100644
index bc549ea..0000000
--- a/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationlist;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
-import com.android.messaging.ui.ListEmptyView;
-import com.android.messaging.datamodel.DataModel;
-
-/**
- * Allow user to pick conversation to which an incoming attachment will be shared.
- */
-public class ShareIntentFragment extends DialogFragment implements ConversationListDataListener,
- ShareIntentAdapter.HostInterface {
- public static final String HIDE_NEW_CONVERSATION_BUTTON_KEY = "hide_conv_button_key";
-
- public interface HostInterface {
- public void onConversationClick(final ConversationListItemData conversationListItemData);
- public void onCreateConversationClick();
- }
-
- private final Binding<ConversationListData> mListBinding = BindingBase.createBinding(this);
- private RecyclerView mRecyclerView;
- private ListEmptyView mEmptyListMessageView;
- private ShareIntentAdapter mAdapter;
- private HostInterface mHost;
- private boolean mDismissed;
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public Dialog onCreateDialog(final Bundle bundle) {
- final Activity activity = getActivity();
- final LayoutInflater inflater = activity.getLayoutInflater();
- View view = inflater.inflate(R.layout.share_intent_conversation_list_view, null);
- mEmptyListMessageView = (ListEmptyView) view.findViewById(R.id.no_conversations_view);
- mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list);
- // The default behavior for default layout param generation by LinearLayoutManager is to
- // provide width and height of WRAP_CONTENT, but this is not desirable for
- // ShareIntentFragment; the view in each row should be a width of MATCH_PARENT so that
- // the entire row is tappable.
- final LinearLayoutManager manager = new LinearLayoutManager(activity) {
- @Override
- public RecyclerView.LayoutParams generateDefaultLayoutParams() {
- return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- }
- };
- mListBinding.getData().init(getLoaderManager(), mListBinding);
- mAdapter = new ShareIntentAdapter(activity, null, this);
- mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list);
- mRecyclerView.setLayoutManager(manager);
- mRecyclerView.setHasFixedSize(true);
- mRecyclerView.setAdapter(mAdapter);
- final Builder dialogBuilder = new AlertDialog.Builder(activity)
- .setView(view)
- .setTitle(R.string.share_intent_activity_label);
-
- final Bundle arguments = getArguments();
- if (arguments == null || !arguments.getBoolean(HIDE_NEW_CONVERSATION_BUTTON_KEY)) {
- dialogBuilder.setPositiveButton(R.string.share_new_message, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mDismissed = true;
- mHost.onCreateConversationClick();
- }
- });
- }
- return dialogBuilder.setNegativeButton(R.string.share_cancel, null)
- .create();
- }
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- if (!mDismissed) {
- final Activity activity = getActivity();
- if (activity != null) {
- activity.finish();
- }
- }
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onDestroy() {
- super.onDestroy();
- mListBinding.unbind();
- }
-
- @Override
- public void onAttach(final Activity activity) {
- super.onAttach(activity);
- if (activity instanceof HostInterface) {
- mHost = (HostInterface) activity;
- }
- mListBinding.bind(DataModel.get().createConversationListData(activity, this, false));
- }
-
- @Override
- public void onConversationListCursorUpdated(final ConversationListData data,
- final Cursor cursor) {
- mListBinding.ensureBound(data);
- mAdapter.swapCursor(cursor);
- updateEmptyListUi(cursor == null || cursor.getCount() == 0);
- }
-
- /**
- * {@inheritDoc} from SharIntentItemView.HostInterface
- */
- @Override
- public void onConversationClicked(final ConversationListItemData conversationListItemData) {
- mHost.onConversationClick(conversationListItemData);
- }
-
- // Show and hide empty list UI as needed with appropriate text based on view specifics
- private void updateEmptyListUi(final boolean isEmpty) {
- if (isEmpty) {
- mEmptyListMessageView.setTextHint(R.string.conversation_list_empty_text);
- mEmptyListMessageView.setVisibility(View.VISIBLE);
- } else {
- mEmptyListMessageView.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void setBlockedParticipantsAvailable(boolean blockedAvailable) {
- }
-}
diff --git a/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java b/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java
deleted file mode 100644
index d727001..0000000
--- a/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationsettings;
-
-import android.app.AlertDialog;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.util.AccessibilityUtil;
-
-public class CopyContactDetailDialog implements DialogInterface.OnClickListener {
-
- private final Context mContext;
- private final String mContactDetail;
-
- public CopyContactDetailDialog(final Context context, final String contactDetail) {
- mContext = context;
- mContactDetail = contactDetail;
- }
-
- public void show() {
- new AlertDialog.Builder(mContext)
- .setView(createBodyView())
- .setTitle(R.string.copy_to_clipboard_dialog_title)
- .setPositiveButton(R.string.copy_to_clipboard, this)
- .show();
- }
-
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- final ClipboardManager clipboard =
- (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
- clipboard.setPrimaryClip(ClipData.newPlainText(null /* label */, mContactDetail));
- }
-
- private View createBodyView() {
- LayoutInflater inflater = (LayoutInflater) mContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- TextView textView = (TextView) inflater.inflate(R.layout.copy_contact_dialog_view, null,
- false);
- textView.setText(mContactDetail);
- final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
- mContext.getResources(), mContactDetail);
- textView.setContentDescription(vocalizedDisplayName);
- return textView;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java
deleted file mode 100644
index f017328..0000000
--- a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.conversationsettings;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-
-/**
- * Shows a list of participants in a conversation.
- */
-public class PeopleAndOptionsActivity extends BugleActionBarActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.people_and_options_activity);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- if (fragment instanceof PeopleAndOptionsFragment) {
- final String conversationId =
- getIntent().getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- Assert.notNull(conversationId);
- final PeopleAndOptionsFragment peopleAndOptionsFragment =
- (PeopleAndOptionsFragment) fragment;
- peopleAndOptionsFragment.setConversationId(conversationId);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // Treat the home press as back press so that when we go back to
- // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
- onBackPressed();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java
deleted file mode 100644
index b86d952..0000000
--- a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationsettings;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.ParticipantListItemData;
-import com.android.messaging.datamodel.data.PeopleAndOptionsData;
-import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
-import com.android.messaging.datamodel.data.PeopleOptionsItemData;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.ui.CompositeAdapter;
-import com.android.messaging.ui.PersonItemView;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.conversation.ConversationActivity;
-import com.android.messaging.util.Assert;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows a list of participants of a conversation and displays options.
- */
-public class PeopleAndOptionsFragment extends Fragment
- implements PeopleAndOptionsDataListener, PeopleOptionsItemView.HostInterface {
- private ListView mListView;
- private OptionsListAdapter mOptionsListAdapter;
- private PeopleListAdapter mPeopleListAdapter;
- private final Binding<PeopleAndOptionsData> mBinding =
- BindingBase.createBinding(this);
-
- private static final int REQUEST_CODE_RINGTONE_PICKER = 1000;
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mBinding.getData().init(getLoaderManager(), mBinding);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.people_and_options_fragment, container, false);
- mListView = (ListView) view.findViewById(android.R.id.list);
- mPeopleListAdapter = new PeopleListAdapter(getActivity());
- mOptionsListAdapter = new OptionsListAdapter();
- final CompositeAdapter compositeAdapter = new CompositeAdapter(getActivity());
- compositeAdapter.addPartition(new PeopleAndOptionsPartition(mOptionsListAdapter,
- R.string.general_settings_title, false));
- compositeAdapter.addPartition(new PeopleAndOptionsPartition(mPeopleListAdapter,
- R.string.participant_list_title, true));
- mListView.setAdapter(compositeAdapter);
- return view;
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_RINGTONE_PICKER) {
- final Parcelable pick = data.getParcelableExtra(
- RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
- final String pickedUri = pick == null ? "" : pick.toString();
- mBinding.getData().setConversationNotificationSound(mBinding, pickedUri);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- public void setConversationId(final String conversationId) {
- Assert.isTrue(getView() == null);
- Assert.notNull(conversationId);
- mBinding.bind(DataModel.get().createPeopleAndOptionsData(conversationId, getActivity(),
- this));
- }
-
- @Override
- public void onOptionsCursorUpdated(final PeopleAndOptionsData data, final Cursor cursor) {
- Assert.isTrue(cursor == null || cursor.getCount() == 1);
- mBinding.ensureBound(data);
- mOptionsListAdapter.swapCursor(cursor);
- }
-
- @Override
- public void onParticipantsListLoaded(final PeopleAndOptionsData data,
- final List<ParticipantData> participants) {
- mBinding.ensureBound(data);
- mPeopleListAdapter.updateParticipants(participants);
- final ParticipantData otherParticipant = participants.size() == 1 ?
- participants.get(0) : null;
- mOptionsListAdapter.setOtherParticipant(otherParticipant);
- }
-
- @Override
- public void onOptionsItemViewClicked(final PeopleOptionsItemData item,
- final boolean isChecked) {
- switch (item.getItemId()) {
- case PeopleOptionsItemData.SETTING_NOTIFICATION_ENABLED:
- mBinding.getData().enableConversationNotifications(mBinding, isChecked);
- break;
-
- case PeopleOptionsItemData.SETTING_NOTIFICATION_SOUND_URI:
- final Intent ringtonePickerIntent = UIIntents.get().getRingtonePickerIntent(
- getString(R.string.notification_sound_pref_title),
- item.getRingtoneUri(), Settings.System.DEFAULT_NOTIFICATION_URI,
- RingtoneManager.TYPE_NOTIFICATION);
- startActivityForResult(ringtonePickerIntent, REQUEST_CODE_RINGTONE_PICKER);
- break;
-
- case PeopleOptionsItemData.SETTING_NOTIFICATION_VIBRATION:
- mBinding.getData().enableConversationNotificationVibration(mBinding,
- isChecked);
- break;
-
- case PeopleOptionsItemData.SETTING_BLOCKED:
- if (item.getOtherParticipant().isBlocked()) {
- mBinding.getData().setDestinationBlocked(mBinding, false);
- break;
- }
- final Resources res = getResources();
- final Activity activity = getActivity();
- new AlertDialog.Builder(activity)
- .setTitle(res.getString(R.string.block_confirmation_title,
- item.getOtherParticipant().getDisplayDestination()))
- .setMessage(res.getString(R.string.block_confirmation_message))
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface arg0, int arg1) {
- mBinding.getData().setDestinationBlocked(mBinding, true);
- activity.setResult(ConversationActivity.FINISH_RESULT_CODE);
- activity.finish();
- }
- })
- .create()
- .show();
- break;
- }
- }
-
- /**
- * A simple adapter that takes a conversation metadata cursor and binds
- * PeopleAndOptionsItemViews to individual COLUMNS of the first cursor record. (Note
- * that this is not a CursorAdapter because it treats individual columns of the cursor as
- * separate options to display for the conversation, e.g. notification settings).
- */
- private class OptionsListAdapter extends BaseAdapter {
- private Cursor mOptionsCursor;
- private ParticipantData mOtherParticipantData;
-
- public Cursor swapCursor(final Cursor newCursor) {
- final Cursor oldCursor = mOptionsCursor;
- if (newCursor != oldCursor) {
- mOptionsCursor = newCursor;
- notifyDataSetChanged();
- }
- return oldCursor;
- }
-
- public void setOtherParticipant(final ParticipantData participantData) {
- if (mOtherParticipantData != participantData) {
- mOtherParticipantData = participantData;
- notifyDataSetChanged();
- }
- }
-
- @Override
- public int getCount() {
- int count = PeopleOptionsItemData.SETTINGS_COUNT;
- if (mOtherParticipantData == null) {
- count--;
- }
- return mOptionsCursor == null ? 0 : count;
- }
-
- @Override
- public Object getItem(final int position) {
- return null;
- }
-
- @Override
- public long getItemId(final int position) {
- return 0;
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- final PeopleOptionsItemView itemView;
- if (convertView != null && convertView instanceof PeopleOptionsItemView) {
- itemView = (PeopleOptionsItemView) convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getActivity()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = (PeopleOptionsItemView)
- inflater.inflate(R.layout.people_options_item_view, parent, false);
- }
- mOptionsCursor.moveToFirst();
- itemView.bind(mOptionsCursor, position, mOtherParticipantData,
- PeopleAndOptionsFragment.this);
- return itemView;
- }
- }
-
- /**
- * An adapter that takes a list of ParticipantData and displays them as a list of
- * ParticipantListItemViews.
- */
- private class PeopleListAdapter extends ArrayAdapter<ParticipantData> {
- public PeopleListAdapter(final Context context) {
- super(context, R.layout.people_list_item_view, new ArrayList<ParticipantData>());
- }
-
- public void updateParticipants(final List<ParticipantData> newList) {
- clear();
- addAll(newList);
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- PersonItemView itemView;
- final ParticipantData item = getItem(position);
- if (convertView != null && convertView instanceof PersonItemView) {
- itemView = (PersonItemView) convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = (PersonItemView) inflater.inflate(R.layout.people_list_item_view, parent,
- false);
- }
- final ParticipantListItemData itemData =
- DataModel.get().createParticipantListItemData(item);
- itemView.bind(itemData);
-
- // Any click on the row should have the same effect as clicking the avatar icon
- final PersonItemView itemViewClosure = itemView;
- itemView.setListener(new PersonItemView.PersonItemViewListener() {
- @Override
- public void onPersonClicked(final PersonItemData data) {
- itemViewClosure.performClickOnAvatar();
- }
-
- @Override
- public boolean onPersonLongClicked(final PersonItemData data) {
- if (mBinding.isBound()) {
- final CopyContactDetailDialog dialog = new CopyContactDetailDialog(
- getContext(), data.getDetails());
- dialog.show();
- return true;
- }
- return false;
- }
- });
- return itemView;
- }
- }
-
- /**
- * Represents a partition/section in the People & Options list (e.g. "general options" and
- * "people in this conversation" sections).
- */
- private class PeopleAndOptionsPartition extends CompositeAdapter.Partition {
- private final int mHeaderResId;
- private final boolean mNeedDivider;
-
- public PeopleAndOptionsPartition(final BaseAdapter adapter, final int headerResId,
- final boolean needDivider) {
- super(true /* showIfEmpty */, true /* hasHeader */, adapter);
- mHeaderResId = headerResId;
- mNeedDivider = needDivider;
- }
-
- @Override
- public View getHeaderView(final View convertView, final ViewGroup parentView) {
- View view = null;
- if (convertView != null && convertView.getId() == R.id.people_and_options_header) {
- view = convertView;
- } else {
- view = LayoutInflater.from(getActivity()).inflate(
- R.layout.people_and_options_section_header, parentView, false);
- }
- final TextView text = (TextView) view.findViewById(R.id.header_text);
- final View divider = view.findViewById(R.id.divider);
- text.setText(mHeaderResId);
- divider.setVisibility(mNeedDivider ? View.VISIBLE : View.GONE);
- return view;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java b/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java
deleted file mode 100644
index 42ecfeb..0000000
--- a/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.conversationsettings;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v7.widget.SwitchCompat;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.PeopleOptionsItemData;
-import com.android.messaging.util.Assert;
-
-/**
- * The view for a single entry in the options section of people & options activity.
- */
-public class PeopleOptionsItemView extends LinearLayout {
- /**
- * Implemented by the host of this view that handles options click event.
- */
- public interface HostInterface {
- void onOptionsItemViewClicked(PeopleOptionsItemData item, boolean isChecked);
- }
-
- private TextView mTitle;
- private TextView mSubtitle;
- private SwitchCompat mSwitch;
- private final PeopleOptionsItemData mData;
- private HostInterface mHostInterface;
-
- public PeopleOptionsItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = DataModel.get().createPeopleOptionsItemData(context);
- }
-
- @Override
- protected void onFinishInflate () {
- mTitle = (TextView) findViewById(R.id.title);
- mSubtitle = (TextView) findViewById(R.id.subtitle);
- mSwitch = (SwitchCompat) findViewById(R.id.switch_button);
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHostInterface.onOptionsItemViewClicked(mData, !mData.getChecked());
- }
- });
- }
-
- public void bind(final Cursor cursor, final int columnIndex, ParticipantData otherParticipant,
- final HostInterface hostInterface) {
- Assert.isTrue(columnIndex < PeopleOptionsItemData.SETTINGS_COUNT && columnIndex >= 0);
- mData.bind(cursor, otherParticipant, columnIndex);
- mHostInterface = hostInterface;
-
- mTitle.setText(mData.getTitle());
- final String subtitle = mData.getSubtitle();
- if (TextUtils.isEmpty(subtitle)) {
- mSubtitle.setVisibility(GONE);
- } else {
- mSubtitle.setVisibility(VISIBLE);
- mSubtitle.setText(subtitle);
- }
-
- if (mData.getCheckable()) {
- mSwitch.setVisibility(VISIBLE);
- mSwitch.setChecked(mData.getChecked());
- } else {
- mSwitch.setVisibility(GONE);
- }
-
- final boolean enabled = mData.getEnabled();
- if (enabled != isEnabled()) {
- mTitle.setEnabled(enabled);
- mSubtitle.setEnabled(enabled);
- mSwitch.setEnabled(enabled);
- setAlpha(enabled ? 1.0f : 0.5f);
- setEnabled(enabled);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java b/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java
deleted file mode 100644
index 485dcf7..0000000
--- a/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.debug;
-
-import android.os.Bundle;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.BaseBugleActivity;
-
-/**
- * Show list of all MmsConfig key/value pairs and allow editing.
- */
-public class DebugMmsConfigActivity extends BaseBugleActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.debug_mmsconfig_activity);
- }
-}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java b/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java
deleted file mode 100644
index 7c54db5..0000000
--- a/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.debug;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.os.Bundle;
-import android.telephony.SubscriptionInfo;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.debug.DebugMmsConfigItemView.MmsConfigItemListener;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Show list of all MmsConfig key/value pairs and allow editing.
- */
-public class DebugMmsConfigFragment extends Fragment {
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View fragmentView = inflater.inflate(R.layout.mms_config_debug_fragment, container,
- false);
- final ListView listView = (ListView) fragmentView.findViewById(android.R.id.list);
- final Spinner spinner = (Spinner) fragmentView.findViewById(R.id.sim_selector);
- final Integer[] subIdArray = getActiveSubIds();
- ArrayAdapter<Integer> spinnerAdapter = new ArrayAdapter<Integer>(getActivity(),
- android.R.layout.simple_spinner_item, subIdArray);
- spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(spinnerAdapter);
- spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- listView.setAdapter(new MmsConfigAdapter(getActivity(), subIdArray[position]));
-
- final int[] mccmnc = PhoneUtils.get(subIdArray[position]).getMccMnc();
- // Set the title with the mcc/mnc
- final TextView title = (TextView) fragmentView.findViewById(R.id.sim_title);
- title.setText("(" + mccmnc[0] + "/" + mccmnc[1] + ") " +
- getActivity().getString(R.string.debug_sub_id_spinner_text));
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
- });
-
- return fragmentView;
- }
-
- public static Integer[] getActiveSubIds() {
- if (!OsUtil.isAtLeastL_MR1()) {
- return new Integer[] { ParticipantData.DEFAULT_SELF_SUB_ID };
- }
- final List<SubscriptionInfo> subRecords =
- PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList();
- if (subRecords == null) {
- return new Integer[0];
- }
- final Integer[] retArray = new Integer[subRecords.size()];
- for (int i = 0; i < subRecords.size(); i++) {
- retArray[i] = subRecords.get(i).getSubscriptionId();
- }
- return retArray;
- }
-
- private class MmsConfigAdapter extends BaseAdapter implements
- DebugMmsConfigItemView.MmsConfigItemListener {
- private final LayoutInflater mInflater;
- private final List<String> mKeys;
- private final MmsConfig mMmsConfig;
-
- public MmsConfigAdapter(Context context, int subId) {
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mMmsConfig = MmsConfig.get(subId);
- mKeys = new ArrayList<>(mMmsConfig.keySet());
- Collections.sort(mKeys);
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- final DebugMmsConfigItemView view;
- if (convertView != null && convertView instanceof DebugMmsConfigItemView) {
- view = (DebugMmsConfigItemView) convertView;
- } else {
- view = (DebugMmsConfigItemView) mInflater.inflate(
- R.layout.debug_mmsconfig_item_view, parent, false);
- }
- final String key = mKeys.get(position);
- view.bind(key,
- MmsConfig.getKeyType(key),
- String.valueOf(mMmsConfig.getValue(key)),
- this);
- return view;
- }
-
- @Override
- public void onValueChanged(String key, String keyType, String value) {
- mMmsConfig.update(key, value, keyType);
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return mKeys.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mKeys.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java b/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java
deleted file mode 100644
index 7b899c0..0000000
--- a/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.debug;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnShowListener;
-import android.text.InputType;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.util.LogUtil;
-
-public class DebugMmsConfigItemView extends LinearLayout implements OnClickListener,
- OnCheckedChangeListener, DialogInterface.OnClickListener {
-
- public interface MmsConfigItemListener {
- void onValueChanged(String key, String keyType, String value);
- }
-
- private TextView mTitle;
- private TextView mTextValue;
- private Switch mSwitch;
- private String mKey;
- private String mKeyType;
- private MmsConfigItemListener mListener;
- private EditText mEditText;
-
- public DebugMmsConfigItemView(Context context, AttributeSet attributeSet) {
- super(context, attributeSet);
- }
-
- @Override
- protected void onFinishInflate () {
- mTitle = (TextView) findViewById(R.id.title);
- mTextValue = (TextView) findViewById(R.id.text_value);
- mSwitch = (Switch) findViewById(R.id.switch_button);
- setOnClickListener(this);
- mSwitch.setOnCheckedChangeListener(this);
- }
-
- public void bind(final String key, final String keyType, final String value,
- final MmsConfigItemListener listener) {
- mListener = listener;
- mKey = key;
- mKeyType = keyType;
- mTitle.setText(key);
-
- switch (keyType) {
- case MmsConfig.KEY_TYPE_BOOL:
- mSwitch.setVisibility(View.VISIBLE);
- mTextValue.setVisibility(View.GONE);
- mSwitch.setChecked(Boolean.valueOf(value));
- break;
- case MmsConfig.KEY_TYPE_STRING:
- case MmsConfig.KEY_TYPE_INT:
- mTextValue.setVisibility(View.VISIBLE);
- mSwitch.setVisibility(View.GONE);
- mTextValue.setText(value);
- break;
- default:
- mTextValue.setVisibility(View.GONE);
- mSwitch.setVisibility(View.GONE);
- LogUtil.e(LogUtil.BUGLE_TAG, "Unexpected keytype: " + keyType);
- break;
- }
- }
-
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- mListener.onValueChanged(mKey, mKeyType, String.valueOf(isChecked));
- }
-
- @Override
- public void onClick(View v) {
- if (MmsConfig.KEY_TYPE_BOOL.equals(mKeyType)) {
- return;
- }
- final Context context = getContext();
- mEditText = new EditText(context);
- mEditText.setText(mTextValue.getText());
- mEditText.setFocusable(true);
- if (MmsConfig.KEY_TYPE_INT.equals(mKeyType)) {
- mEditText.setInputType(InputType.TYPE_CLASS_PHONE);
- } else {
- mEditText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
- }
- final AlertDialog dialog = new AlertDialog.Builder(context)
- .setTitle(mKey)
- .setView(mEditText)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, null)
- .create();
- dialog.setOnShowListener(new OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- mEditText.requestFocus();
- mEditText.selectAll();
- ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE))
- .toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
- }
- });
- dialog.show();
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mListener.onValueChanged(mKey, mKeyType, mEditText.getText().toString());
- }
-}
diff --git a/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java b/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java
deleted file mode 100644
index 1aa8be3..0000000
--- a/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.debug;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.telephony.SmsMessage;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.action.ReceiveMmsMessageAction;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.receiver.SmsReceiver;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Class that displays UI for choosing SMS/MMS dump files for debugging
- */
-public class DebugSmsMmsFromDumpFileDialogFragment extends DialogFragment {
- public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
- public static final String KEY_DUMP_FILES = "dump_files";
- public static final String KEY_ACTION = "action";
-
- public static final String ACTION_LOAD = "load";
- public static final String ACTION_EMAIL = "email";
-
- private String[] mDumpFiles;
- private String mAction;
-
- public static DebugSmsMmsFromDumpFileDialogFragment newInstance(final String[] dumpFiles,
- final String action) {
- final DebugSmsMmsFromDumpFileDialogFragment frag =
- new DebugSmsMmsFromDumpFileDialogFragment();
- final Bundle args = new Bundle();
- args.putSerializable(KEY_DUMP_FILES, dumpFiles);
- args.putString(KEY_ACTION, action);
- frag.setArguments(args);
- return frag;
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final Bundle args = getArguments();
- mDumpFiles = (String[]) args.getSerializable(KEY_DUMP_FILES);
- mAction = args.getString(KEY_ACTION);
-
- final LayoutInflater inflater = getActivity().getLayoutInflater();
- final View layout = inflater.inflate(
- R.layout.debug_sms_mms_from_dump_file_dialog, null/*root*/);
- final ListView list = (ListView) layout.findViewById(R.id.dump_file_list);
- list.setAdapter(new DumpFileListAdapter(getActivity(), mDumpFiles));
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- final Resources resources = getResources();
- if (ACTION_LOAD.equals(mAction)) {
- builder.setTitle(resources.getString(
- R.string.load_sms_mms_from_dump_file_dialog_title));
- } else if (ACTION_EMAIL.equals(mAction)) {
- builder.setTitle(resources.getString(
- R.string.email_sms_mms_from_dump_file_dialog_title));
- }
- builder.setView(layout);
- return builder.create();
- }
-
- private class DumpFileListAdapter extends ArrayAdapter<String> {
- public DumpFileListAdapter(final Context context, final String[] dumpFiles) {
- super(context, R.layout.sms_mms_dump_file_list_item, dumpFiles);
- }
-
- @Override
- public View getView(final int position, final View view, final ViewGroup parent) {
- TextView actionItemView;
- if (view == null || !(view instanceof TextView)) {
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- actionItemView = (TextView) inflater.inflate(
- R.layout.sms_mms_dump_file_list_item, parent, false);
- } else {
- actionItemView = (TextView) view;
- }
-
- final String file = getItem(position);
- actionItemView.setText(file);
- actionItemView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- dismiss();
- if (ACTION_LOAD.equals(mAction)) {
- receiveFromDumpFile(file);
- } else if (ACTION_EMAIL.equals(mAction)) {
- emailDumpFile(file);
- }
- }
- });
- return actionItemView;
- }
- }
-
- /**
- * Load MMS/SMS from the dump file
- */
- private void receiveFromDumpFile(final String dumpFileName) {
- if (dumpFileName.startsWith(MmsUtils.SMS_DUMP_PREFIX)) {
- final SmsMessage[] messages = DebugUtils.retreiveSmsFromDumpFile(dumpFileName);
- if (messages != null) {
- SmsReceiver.deliverSmsMessages(getActivity(), ParticipantData.DEFAULT_SELF_SUB_ID,
- 0, messages);
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "receiveFromDumpFile: invalid sms dump file " + dumpFileName);
- }
- } else if (dumpFileName.startsWith(MmsUtils.MMS_DUMP_PREFIX)) {
- final byte[] data = MmsUtils.createDebugNotificationInd(dumpFileName);
- if (data != null) {
- final ReceiveMmsMessageAction action = new ReceiveMmsMessageAction(
- ParticipantData.DEFAULT_SELF_SUB_ID, data);
- action.start();
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "receiveFromDumpFile: invalid mms dump file " + dumpFileName);
- }
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "receiveFromDumpFile: invalid dump file name " + dumpFileName);
- }
- }
-
- /**
- * Launch email app to send the dump file
- */
- private void emailDumpFile(final String file) {
- final Resources resources = getResources();
- final String fileLocation = "file://"
- + Environment.getExternalStorageDirectory() + "/" + file;
- final Intent sharingIntent = new Intent(Intent.ACTION_SEND);
- sharingIntent.setType(APPLICATION_OCTET_STREAM);
- sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(fileLocation));
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT,
- resources.getString(R.string.email_sms_mms_dump_file_subject));
- getActivity().startActivity(Intent.createChooser(sharingIntent,
- resources.getString(R.string.email_sms_mms_dump_file_chooser_title)));
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java b/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java
deleted file mode 100644
index a211058..0000000
--- a/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.mediapicker;
-
-import com.google.common.base.Preconditions;
-
-import javax.annotation.concurrent.ThreadSafe;
-
-/**
- * Keeps track of the speech level as last observed by the recognition
- * engine as microphone data flows through it. Can be polled by the UI to
- * animate its views.
- */
-@ThreadSafe
-public class AudioLevelSource {
- private volatile int mSpeechLevel;
- private volatile Listener mListener;
-
- public static final int LEVEL_UNKNOWN = -1;
-
- public interface Listener {
- void onSpeechLevel(int speechLevel);
- }
-
- public void setSpeechLevel(int speechLevel) {
- Preconditions.checkArgument(speechLevel >= 0 && speechLevel <= 100 ||
- speechLevel == LEVEL_UNKNOWN);
- mSpeechLevel = speechLevel;
- maybeNotify();
- }
-
- public int getSpeechLevel() {
- return mSpeechLevel;
- }
-
- public void reset() {
- setSpeechLevel(LEVEL_UNKNOWN);
- }
-
- public boolean isValid() {
- return mSpeechLevel > 0;
- }
-
- private void maybeNotify() {
- final Listener l = mListener;
- if (l != null) {
- l.onSpeechLevel(mSpeechLevel);
- }
- }
-
- public synchronized void setListener(Listener listener) {
- mListener = listener;
- }
-
- public synchronized void clearListener(Listener listener) {
- if (mListener == listener) {
- mListener = null;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java b/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
deleted file mode 100644
index 5d79293..0000000
--- a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.Manifest;
-import android.content.pm.PackageManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Chooser which allows the user to record audio
- */
-class AudioMediaChooser extends MediaChooser implements
- AudioRecordView.HostInterface {
- private View mEnabledView;
- private View mMissingPermissionView;
-
- AudioMediaChooser(final MediaPicker mediaPicker) {
- super(mediaPicker);
- }
-
- @Override
- public int getSupportedMediaTypes() {
- return MediaPicker.MEDIA_TYPE_AUDIO;
- }
-
- @Override
- public int getIconResource() {
- return R.drawable.ic_audio_light;
- }
-
- @Override
- public int getIconDescriptionResource() {
- return R.string.mediapicker_audioChooserDescription;
- }
-
- @Override
- public void onAudioRecorded(final MessagePartData item) {
- mMediaPicker.dispatchItemsSelected(item, true);
- }
-
- @Override
- public void setThemeColor(final int color) {
- if (mView != null) {
- ((AudioRecordView) mView).setThemeColor(color);
- }
- }
-
- @Override
- protected View createView(final ViewGroup container) {
- final LayoutInflater inflater = getLayoutInflater();
- final AudioRecordView view = (AudioRecordView) inflater.inflate(
- R.layout.mediapicker_audio_chooser,
- container /* root */,
- false /* attachToRoot */);
- view.setHostInterface(this);
- view.setThemeColor(mMediaPicker.getConversationThemeColor());
- mEnabledView = view.findViewById(R.id.mediapicker_enabled);
- mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
- return view;
- }
-
- @Override
- int getActionBarTitleResId() {
- return R.string.mediapicker_audio_title;
- }
-
- @Override
- public boolean isHandlingTouch() {
- // Whenever the user is in the process of recording audio, we want to allow the user
- // to move the finger within the panel without interpreting that as dragging the media
- // picker panel.
- return ((AudioRecordView) mView).shouldHandleTouch();
- }
-
- @Override
- public void stopTouchHandling() {
- ((AudioRecordView) mView).stopTouchHandling();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (mView != null) {
- ((AudioRecordView) mView).onPause();
- }
- }
-
- @Override
- protected void setSelected(final boolean selected) {
- super.setSelected(selected);
- if (selected && !OsUtil.hasRecordAudioPermission()) {
- requestRecordAudioPermission();
- }
- }
-
- private void requestRecordAudioPermission() {
- mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
- MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
- }
-
- @Override
- protected void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
- final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
- mEnabledView.setVisibility(permissionGranted ? View.VISIBLE : View.GONE);
- mMissingPermissionView.setVisibility(permissionGranted ? View.GONE : View.VISIBLE);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioRecordView.java b/src/com/android/messaging/ui/mediapicker/AudioRecordView.java
deleted file mode 100644
index fba493f..0000000
--- a/src/com/android/messaging/ui/mediapicker/AudioRecordView.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaUtil;
-import com.android.messaging.util.MediaUtil.OnCompletionListener;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Hosts an audio recorder with tap and hold to record functionality.
- */
-public class AudioRecordView extends FrameLayout implements
- MediaRecorder.OnErrorListener,
- MediaRecorder.OnInfoListener {
- /**
- * An interface that communicates with the hosted AudioRecordView.
- */
- public interface HostInterface extends DraftMessageSubscriptionDataProvider {
- void onAudioRecorded(final MessagePartData item);
- }
-
- /** The initial state, the user may press and hold to start recording */
- private static final int MODE_IDLE = 1;
-
- /** The user has pressed the record button and we are playing the sound indicating the
- * start of recording session. Don't record yet since we don't want the beeping sound
- * to get into the recording. */
- private static final int MODE_STARTING = 2;
-
- /** When the user is actively recording */
- private static final int MODE_RECORDING = 3;
-
- /** When the user has finished recording, we need to record for some additional time. */
- private static final int MODE_STOPPING = 4;
-
- // Bug: 16020175: The framework's MediaRecorder would cut off the ending portion of the
- // recorded audio by about half a second. To mitigate this issue, we continue the recording
- // for some extra time before stopping it.
- private static final int AUDIO_RECORD_ENDING_BUFFER_MILLIS = 500;
-
- /**
- * The minimum duration of any recording. Below this threshold, it will be treated as if the
- * user clicked the record button and inform the user to tap and hold to record.
- */
- private static final int AUDIO_RECORD_MINIMUM_DURATION_MILLIS = 300;
-
- // For accessibility, the touchable record button is bigger than the record button visual.
- private ImageView mRecordButtonVisual;
- private View mRecordButton;
- private SoundLevels mSoundLevels;
- private TextView mHintTextView;
- private PausableChronometer mTimerTextView;
- private LevelTrackingMediaRecorder mMediaRecorder;
- private long mAudioRecordStartTimeMillis;
-
- private int mCurrentMode = MODE_IDLE;
- private HostInterface mHostInterface;
- private int mThemeColor;
-
- public AudioRecordView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mMediaRecorder = new LevelTrackingMediaRecorder();
- }
-
- public void setHostInterface(final HostInterface hostInterface) {
- mHostInterface = hostInterface;
- }
-
- @VisibleForTesting
- public void testSetMediaRecorder(final LevelTrackingMediaRecorder recorder) {
- mMediaRecorder = recorder;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSoundLevels = (SoundLevels) findViewById(R.id.sound_levels);
- mRecordButtonVisual = (ImageView) findViewById(R.id.record_button_visual);
- mRecordButton = findViewById(R.id.record_button);
- mHintTextView = (TextView) findViewById(R.id.hint_text);
- mTimerTextView = (PausableChronometer) findViewById(R.id.timer_text);
- mSoundLevels.setLevelSource(mMediaRecorder.getLevelSource());
- mRecordButton.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(final View v, final MotionEvent event) {
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- onRecordButtonTouchDown();
-
- // Don't let the record button handle the down event to let it fall through
- // so that we can handle it for the entire panel in onTouchEvent(). This is
- // done so that: 1) the user taps on the record button to start recording
- // 2) the entire panel owns the touch event so we'd keep recording even
- // if the user moves outside the button region.
- return false;
- }
- return false;
- }
- });
- }
-
- @Override
- public boolean onTouchEvent(final MotionEvent event) {
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- return shouldHandleTouch();
-
- case MotionEvent.ACTION_MOVE:
- return true;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- return onRecordButtonTouchUp();
- }
- return super.onTouchEvent(event);
- }
-
- public void onPause() {
- // The conversation draft cannot take any updates when it's paused. Therefore, forcibly
- // stop recording on pause.
- stopRecording();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- stopRecording();
- }
-
- private boolean isRecording() {
- return mMediaRecorder.isRecording() && mCurrentMode == MODE_RECORDING;
- }
-
- public boolean shouldHandleTouch() {
- return mCurrentMode != MODE_IDLE;
- }
-
- public void stopTouchHandling() {
- setMode(MODE_IDLE);
- stopRecording();
- }
-
- private void setMode(final int mode) {
- if (mCurrentMode != mode) {
- mCurrentMode = mode;
- updateVisualState();
- }
- }
-
- private void updateVisualState() {
- switch (mCurrentMode) {
- case MODE_IDLE:
- mHintTextView.setVisibility(VISIBLE);
- mHintTextView.setTypeface(null, Typeface.NORMAL);
- mTimerTextView.setVisibility(GONE);
- mSoundLevels.setEnabled(false);
- mTimerTextView.stop();
- break;
-
- case MODE_RECORDING:
- case MODE_STOPPING:
- mHintTextView.setVisibility(GONE);
- mTimerTextView.setVisibility(VISIBLE);
- mSoundLevels.setEnabled(true);
- mTimerTextView.restart();
- break;
-
- case MODE_STARTING:
- break; // No-Op.
-
- default:
- Assert.fail("invalid mode for AudioRecordView!");
- break;
- }
- updateRecordButtonAppearance();
- }
-
- public void setThemeColor(final int color) {
- mThemeColor = color;
- updateRecordButtonAppearance();
- }
-
- private void updateRecordButtonAppearance() {
- final Drawable foregroundDrawable = getResources().getDrawable(R.drawable.ic_mp_audio_mic);
- final GradientDrawable backgroundDrawable = ((GradientDrawable) getResources()
- .getDrawable(R.drawable.audio_record_control_button_background));
- if (isRecording()) {
- foregroundDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
- backgroundDrawable.setColor(mThemeColor);
- } else {
- foregroundDrawable.setColorFilter(mThemeColor, PorterDuff.Mode.SRC_ATOP);
- backgroundDrawable.setColor(Color.WHITE);
- }
- mRecordButtonVisual.setImageDrawable(foregroundDrawable);
- mRecordButtonVisual.setBackground(backgroundDrawable);
- }
-
- @VisibleForTesting
- boolean onRecordButtonTouchDown() {
- if (!mMediaRecorder.isRecording() && mCurrentMode == MODE_IDLE) {
- setMode(MODE_STARTING);
- playAudioStartSound(new OnCompletionListener() {
- @Override
- public void onCompletion() {
- // Double-check the current mode before recording since the user may have
- // lifted finger from the button before the beeping sound is played through.
- final int maxSize = MmsConfig.get(mHostInterface.getConversationSelfSubId())
- .getMaxMessageSize();
- if (mCurrentMode == MODE_STARTING &&
- mMediaRecorder.startRecording(AudioRecordView.this,
- AudioRecordView.this, maxSize)) {
- setMode(MODE_RECORDING);
- }
- }
- });
- mAudioRecordStartTimeMillis = System.currentTimeMillis();
- return true;
- }
- return false;
- }
-
- @VisibleForTesting
- boolean onRecordButtonTouchUp() {
- if (System.currentTimeMillis() - mAudioRecordStartTimeMillis <
- AUDIO_RECORD_MINIMUM_DURATION_MILLIS) {
- // The recording is too short, bolden the hint text to instruct the user to
- // "tap+hold" to record audio.
- final Uri outputUri = stopRecording();
- if (outputUri != null) {
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- Factory.get().getApplicationContext().getContentResolver().delete(
- outputUri, null, null);
- }
- });
- }
- setMode(MODE_IDLE);
- mHintTextView.setTypeface(null, Typeface.BOLD);
- } else if (isRecording()) {
- // Record for some extra time to ensure the ending part is saved.
- setMode(MODE_STOPPING);
- ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- onFinishedRecording();
- }
- }, AUDIO_RECORD_ENDING_BUFFER_MILLIS);
- } else {
- setMode(MODE_IDLE);
- }
- return true;
- }
-
- private Uri stopRecording() {
- if (mMediaRecorder.isRecording()) {
- return mMediaRecorder.stopRecording();
- }
- return null;
- }
-
- @Override // From MediaRecorder.OnInfoListener
- public void onInfo(final MediaRecorder mr, final int what, final int extra) {
- if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
- // Max size reached. Finish recording immediately.
- LogUtil.i(LogUtil.BUGLE_TAG, "Max size reached while recording audio");
- onFinishedRecording();
- } else {
- // These are unknown errors.
- onErrorWhileRecording(what, extra);
- }
- }
-
- @Override // From MediaRecorder.OnErrorListener
- public void onError(final MediaRecorder mr, final int what, final int extra) {
- onErrorWhileRecording(what, extra);
- }
-
- private void onErrorWhileRecording(final int what, final int extra) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error occurred during audio recording what=" + what +
- ", extra=" + extra);
- UiUtils.showToastAtBottom(R.string.audio_recording_error);
- setMode(MODE_IDLE);
- stopRecording();
- }
-
- private void onFinishedRecording() {
- final Uri outputUri = stopRecording();
- if (outputUri != null) {
- final Rect startRect = new Rect();
- mRecordButtonVisual.getGlobalVisibleRect(startRect);
- final MediaPickerMessagePartData audioItem =
- new MediaPickerMessagePartData(startRect,
- ContentType.AUDIO_3GPP, outputUri, 0, 0);
- mHostInterface.onAudioRecorded(audioItem);
- }
- playAudioEndSound();
- setMode(MODE_IDLE);
- }
-
- private void playAudioStartSound(final OnCompletionListener completionListener) {
- MediaUtil.get().playSound(getContext(), R.raw.audio_initiate, completionListener);
- }
-
- private void playAudioEndSound() {
- MediaUtil.get().playSound(getContext(), R.raw.audio_end, null);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraManager.java b/src/com/android/messaging/ui/mediapicker/CameraManager.java
deleted file mode 100644
index 166ebd7..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraManager.java
+++ /dev/null
@@ -1,1200 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.Surface;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.mediapicker.camerafocus.FocusOverlayManager;
-import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Class which manages interactions with the camera, but does not do any UI. This class is
- * designed to be a singleton to ensure there is one component managing the camera and releasing
- * the native resources.
- * In order to acquire a camera, a caller must:
- * <ul>
- * <li>Call selectCamera to select front or back camera
- * <li>Call setSurface to control where the preview is shown
- * <li>Call openCamera to request the camera start preview
- * </ul>
- * Callers should call onPause and onResume to ensure that the camera is release while the activity
- * is not active.
- * This class is not thread safe. It should only be called from one thread (the UI thread or test
- * thread)
- */
-class CameraManager implements FocusOverlayManager.Listener {
- /**
- * Wrapper around the framework camera API to allow mocking different hardware scenarios while
- * unit testing
- */
- interface CameraWrapper {
- int getNumberOfCameras();
- void getCameraInfo(int index, CameraInfo cameraInfo);
- Camera open(int cameraId);
- /** Add a wrapper for release because a final method cannot be mocked */
- void release(Camera camera);
- }
-
- /**
- * Callbacks for the camera manager listener
- */
- interface CameraManagerListener {
- void onCameraError(int errorCode, Exception e);
- void onCameraChanged();
- }
-
- /**
- * Callback when taking image or video
- */
- interface MediaCallback {
- static final int MEDIA_CAMERA_CHANGED = 1;
- static final int MEDIA_NO_DATA = 2;
-
- void onMediaReady(Uri uriToMedia, String contentType, int width, int height);
- void onMediaFailed(Exception exception);
- void onMediaInfo(int what);
- }
-
- // Error codes
- static final int ERROR_OPENING_CAMERA = 1;
- static final int ERROR_SHOWING_PREVIEW = 2;
- static final int ERROR_INITIALIZING_VIDEO = 3;
- static final int ERROR_STORAGE_FAILURE = 4;
- static final int ERROR_RECORDING_VIDEO = 5;
- static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 6;
- static final int ERROR_TAKING_PICTURE = 7;
-
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final int NO_CAMERA_SELECTED = -1;
-
- private static CameraManager sInstance;
-
- /** Default camera wrapper which directs calls to the framework APIs */
- private static CameraWrapper sCameraWrapper = new CameraWrapper() {
- @Override
- public int getNumberOfCameras() {
- return Camera.getNumberOfCameras();
- }
-
- @Override
- public void getCameraInfo(final int index, final CameraInfo cameraInfo) {
- Camera.getCameraInfo(index, cameraInfo);
- }
-
- @Override
- public Camera open(final int cameraId) {
- return Camera.open(cameraId);
- }
-
- @Override
- public void release(final Camera camera) {
- camera.release();
- }
- };
-
- /** The CameraInfo for the currently selected camera */
- private final CameraInfo mCameraInfo;
-
- /**
- * The index of the selected camera or NO_CAMERA_SELECTED if a camera hasn't been selected yet
- */
- private int mCameraIndex;
-
- /** True if the device has front and back cameras */
- private final boolean mHasFrontAndBackCamera;
-
- /** True if the camera should be open (may not yet be actually open) */
- private boolean mOpenRequested;
-
- /** True if the camera is requested to be in video mode */
- private boolean mVideoModeRequested;
-
- /** The media recorder for video mode */
- private MmsVideoRecorder mMediaRecorder;
-
- /** Callback to call with video recording updates */
- private MediaCallback mVideoCallback;
-
- /** The preview view to show the preview on */
- private CameraPreview mCameraPreview;
-
- /** The helper classs to handle orientation changes */
- private OrientationHandler mOrientationHandler;
-
- /** Tracks whether the preview has hardware acceleration */
- private boolean mIsHardwareAccelerationSupported;
-
- /**
- * The task for opening the camera, so it doesn't block the UI thread
- * Using AsyncTask rather than SafeAsyncTask because the tasks need to be serialized, but don't
- * need to be on the UI thread
- * TODO: If we have other AyncTasks (not SafeAsyncTasks) this may contend and we may
- * need to create a dedicated thread, or synchronize the threads in the thread pool
- */
- private AsyncTask<Integer, Void, Camera> mOpenCameraTask;
-
- /**
- * The camera index that is queued to be opened, but not completed yet, or NO_CAMERA_SELECTED if
- * no open task is pending
- */
- private int mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
-
- /** The instance of the currently opened camera */
- private Camera mCamera;
-
- /** The rotation of the screen relative to the camera's natural orientation */
- private int mRotation;
-
- /** The callback to notify when errors or other events occur */
- private CameraManagerListener mListener;
-
- /** True if the camera is currently in the process of taking an image */
- private boolean mTakingPicture;
-
- /** Provides subscription-related data to access per-subscription configurations. */
- private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
-
- /** Manages auto focus visual and behavior */
- private final FocusOverlayManager mFocusOverlayManager;
-
- private CameraManager() {
- mCameraInfo = new CameraInfo();
- mCameraIndex = NO_CAMERA_SELECTED;
-
- // Check to see if a front and back camera exist
- boolean hasFrontCamera = false;
- boolean hasBackCamera = false;
- final CameraInfo cameraInfo = new CameraInfo();
- final int cameraCount = sCameraWrapper.getNumberOfCameras();
- try {
- for (int i = 0; i < cameraCount; i++) {
- sCameraWrapper.getCameraInfo(i, cameraInfo);
- if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) {
- hasFrontCamera = true;
- } else if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
- hasBackCamera = true;
- }
- if (hasFrontCamera && hasBackCamera) {
- break;
- }
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "Unable to load camera info", e);
- }
- mHasFrontAndBackCamera = hasFrontCamera && hasBackCamera;
- mFocusOverlayManager = new FocusOverlayManager(this, Looper.getMainLooper());
-
- // Assume the best until we are proven otherwise
- mIsHardwareAccelerationSupported = true;
- }
-
- /** Gets the singleton instance */
- static CameraManager get() {
- if (sInstance == null) {
- sInstance = new CameraManager();
- }
- return sInstance;
- }
-
- /** Allows tests to inject a custom camera wrapper */
- @VisibleForTesting
- static void setCameraWrapper(final CameraWrapper cameraWrapper) {
- sCameraWrapper = cameraWrapper;
- sInstance = null;
- }
-
- /**
- * Sets the surface to use to display the preview
- * This must only be called AFTER the CameraPreview has a texture ready
- * @param preview The preview surface view
- */
- void setSurface(final CameraPreview preview) {
- if (preview == mCameraPreview) {
- return;
- }
-
- if (preview != null) {
- Assert.isTrue(preview.isValid());
- preview.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(final View view, final MotionEvent motionEvent) {
- if ((motionEvent.getActionMasked() & MotionEvent.ACTION_UP) ==
- MotionEvent.ACTION_UP) {
- mFocusOverlayManager.setPreviewSize(view.getWidth(), view.getHeight());
- mFocusOverlayManager.onSingleTapUp(
- (int) motionEvent.getX() + view.getLeft(),
- (int) motionEvent.getY() + view.getTop());
- }
- return true;
- }
- });
- }
- mCameraPreview = preview;
- tryShowPreview();
- }
-
- void setRenderOverlay(final RenderOverlay renderOverlay) {
- mFocusOverlayManager.setFocusRenderer(renderOverlay != null ?
- renderOverlay.getPieRenderer() : null);
- }
-
- /** Convenience function to swap between front and back facing cameras */
- void swapCamera() {
- Assert.isTrue(mCameraIndex >= 0);
- selectCamera(mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT ?
- CameraInfo.CAMERA_FACING_BACK :
- CameraInfo.CAMERA_FACING_FRONT);
- }
-
- /**
- * Selects the first camera facing the desired direction, or the first camera if there is no
- * camera in the desired direction
- * @param desiredFacing One of the CameraInfo.CAMERA_FACING_* constants
- * @return True if a camera was selected, or false if selecting a camera failed
- */
- boolean selectCamera(final int desiredFacing) {
- try {
- // We already selected a camera facing that direction
- if (mCameraIndex >= 0 && mCameraInfo.facing == desiredFacing) {
- return true;
- }
-
- final int cameraCount = sCameraWrapper.getNumberOfCameras();
- Assert.isTrue(cameraCount > 0);
-
- mCameraIndex = NO_CAMERA_SELECTED;
- setCamera(null);
- final CameraInfo cameraInfo = new CameraInfo();
- for (int i = 0; i < cameraCount; i++) {
- sCameraWrapper.getCameraInfo(i, cameraInfo);
- if (cameraInfo.facing == desiredFacing) {
- mCameraIndex = i;
- sCameraWrapper.getCameraInfo(i, mCameraInfo);
- break;
- }
- }
-
- // There's no camera in the desired facing direction, just select the first camera
- // regardless of direction
- if (mCameraIndex < 0) {
- mCameraIndex = 0;
- sCameraWrapper.getCameraInfo(0, mCameraInfo);
- }
-
- if (mOpenRequested) {
- // The camera is open, so reopen with the newly selected camera
- openCamera();
- }
- return true;
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.selectCamera", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- return false;
- }
- }
-
- int getCameraIndex() {
- return mCameraIndex;
- }
-
- void selectCameraByIndex(final int cameraIndex) {
- if (mCameraIndex == cameraIndex) {
- return;
- }
-
- try {
- mCameraIndex = cameraIndex;
- sCameraWrapper.getCameraInfo(mCameraIndex, mCameraInfo);
- if (mOpenRequested) {
- openCamera();
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.selectCameraByIndex", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
-
- @VisibleForTesting
- CameraInfo getCameraInfo() {
- if (mCameraIndex == NO_CAMERA_SELECTED) {
- return null;
- }
- return mCameraInfo;
- }
-
- /** @return True if this device has camera capabilities */
- boolean hasAnyCamera() {
- return sCameraWrapper.getNumberOfCameras() > 0;
- }
-
- /** @return True if the device has both a front and back camera */
- boolean hasFrontAndBackCamera() {
- return mHasFrontAndBackCamera;
- }
-
- /**
- * Opens the camera on a separate thread and initiates the preview if one is available
- */
- void openCamera() {
- if (mCameraIndex == NO_CAMERA_SELECTED) {
- // Ensure a selected camera if none is currently selected. This may happen if the
- // camera chooser is not the default media chooser.
- selectCamera(CameraInfo.CAMERA_FACING_BACK);
- }
- mOpenRequested = true;
- // We're already opening the camera or already have the camera handle, nothing more to do
- if (mPendingOpenCameraIndex == mCameraIndex || mCamera != null) {
- return;
- }
-
- // True if the task to open the camera has to be delayed until the current one completes
- boolean delayTask = false;
-
- // Cancel any previous open camera tasks
- if (mOpenCameraTask != null) {
- mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
- delayTask = true;
- }
-
- mPendingOpenCameraIndex = mCameraIndex;
- mOpenCameraTask = new AsyncTask<Integer, Void, Camera>() {
- private Exception mException;
-
- @Override
- protected Camera doInBackground(final Integer... params) {
- try {
- final int cameraIndex = params[0];
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Opening camera " + mCameraIndex);
- }
- return sCameraWrapper.open(cameraIndex);
- } catch (final Exception e) {
- LogUtil.e(TAG, "Exception while opening camera", e);
- mException = e;
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(final Camera camera) {
- // If we completed, but no longer want this camera, then release the camera
- if (mOpenCameraTask != this || !mOpenRequested) {
- releaseCamera(camera);
- cleanup();
- return;
- }
-
- cleanup();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Opened camera " + mCameraIndex + " " + (camera != null));
- }
-
- setCamera(camera);
- if (camera == null) {
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, mException);
- }
- LogUtil.e(TAG, "Error opening camera");
- }
- }
-
- @Override
- protected void onCancelled() {
- super.onCancelled();
- cleanup();
- }
-
- private void cleanup() {
- mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
- if (mOpenCameraTask != null && mOpenCameraTask.getStatus() == Status.PENDING) {
- // If there's another task waiting on this one to complete, start it now
- mOpenCameraTask.execute(mCameraIndex);
- } else {
- mOpenCameraTask = null;
- }
-
- }
- };
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Start opening camera " + mCameraIndex);
- }
-
- if (!delayTask) {
- mOpenCameraTask.execute(mCameraIndex);
- }
- }
-
- boolean isVideoMode() {
- return mVideoModeRequested;
- }
-
- boolean isRecording() {
- return mVideoModeRequested && mVideoCallback != null;
- }
-
- void setVideoMode(final boolean videoMode) {
- if (mVideoModeRequested == videoMode) {
- return;
- }
- mVideoModeRequested = videoMode;
- tryInitOrCleanupVideoMode();
- }
-
- /** Closes the camera releasing the resources it uses */
- void closeCamera() {
- mOpenRequested = false;
- setCamera(null);
- }
-
- /** Temporarily closes the camera if it is open */
- void onPause() {
- setCamera(null);
- }
-
- /** Reopens the camera if it was opened when onPause was called */
- void onResume() {
- if (mOpenRequested) {
- openCamera();
- }
- }
-
- /**
- * Sets the listener which will be notified of errors or other events in the camera
- * @param listener The listener to notify
- */
- void setListener(final CameraManagerListener listener) {
- Assert.isMainThread();
- mListener = listener;
- if (!mIsHardwareAccelerationSupported && mListener != null) {
- mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
- }
- }
-
- void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
- mSubscriptionDataProvider = provider;
- }
-
- void takePicture(final float heightPercent, @NonNull final MediaCallback callback) {
- Assert.isTrue(!mVideoModeRequested);
- Assert.isTrue(!mTakingPicture);
- Assert.notNull(callback);
- if (mCamera == null) {
- // The caller should have checked isCameraAvailable first, but just in case, protect
- // against a null camera by notifying the callback that taking the picture didn't work
- callback.onMediaFailed(null);
- return;
- }
- final Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
- @Override
- public void onPictureTaken(final byte[] bytes, final Camera camera) {
- mTakingPicture = false;
- if (mCamera != camera) {
- // This may happen if the camera was changed between front/back while the
- // picture is being taken.
- callback.onMediaInfo(MediaCallback.MEDIA_CAMERA_CHANGED);
- return;
- }
-
- if (bytes == null) {
- callback.onMediaInfo(MediaCallback.MEDIA_NO_DATA);
- return;
- }
-
- final Camera.Size size = camera.getParameters().getPictureSize();
- int width;
- int height;
- if (mRotation == 90 || mRotation == 270) {
- width = size.height;
- height = size.width;
- } else {
- width = size.width;
- height = size.height;
- }
- new ImagePersistTask(
- width, height, heightPercent, bytes, mCameraPreview.getContext(), callback)
- .executeOnThreadPool();
- }
- };
-
- mTakingPicture = true;
- try {
- mCamera.takePicture(
- null /* shutter */,
- null /* raw */,
- null /* postView */,
- jpegCallback);
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.takePicture", e);
- mTakingPicture = false;
- if (mListener != null) {
- mListener.onCameraError(ERROR_TAKING_PICTURE, e);
- }
- }
- }
-
- void startVideo(final MediaCallback callback) {
- Assert.notNull(callback);
- Assert.isTrue(!isRecording());
- mVideoCallback = callback;
- tryStartVideoCapture();
- }
-
- /**
- * Asynchronously releases a camera
- * @param camera The camera to release
- */
- private void releaseCamera(final Camera camera) {
- if (camera == null) {
- return;
- }
-
- mFocusOverlayManager.onCameraReleased();
-
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(final Void... params) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Releasing camera " + mCameraIndex);
- }
- sCameraWrapper.release(camera);
- return null;
- }
- }.execute();
- }
-
- private void releaseMediaRecorder(final boolean cleanupFile) {
- if (mMediaRecorder == null) {
- return;
- }
- mVideoModeRequested = false;
-
- if (cleanupFile) {
- mMediaRecorder.cleanupTempFile();
- if (mVideoCallback != null) {
- final MediaCallback callback = mVideoCallback;
- mVideoCallback = null;
- // Notify the callback that we've stopped recording
- callback.onMediaReady(null /*uri*/, null /*contentType*/, 0 /*width*/,
- 0 /*height*/);
- }
- }
-
- mMediaRecorder.release();
- mMediaRecorder = null;
-
- if (mCamera != null) {
- try {
- mCamera.reconnect();
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.releaseMediaRecorder", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.releaseMediaRecorder", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
- restoreRequestedOrientation();
- }
-
- /** Updates the orientation of the camera to match the orientation of the device */
- private void updateCameraOrientation() {
- if (mCamera == null || mCameraPreview == null || mTakingPicture) {
- return;
- }
-
- final WindowManager windowManager =
- (WindowManager) mCameraPreview.getContext().getSystemService(
- Context.WINDOW_SERVICE);
-
- int degrees = 0;
- switch (windowManager.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_0: degrees = 0; break;
- case Surface.ROTATION_90: degrees = 90; break;
- case Surface.ROTATION_180: degrees = 180; break;
- case Surface.ROTATION_270: degrees = 270; break;
- }
-
- // The display orientation of the camera (this controls the preview image).
- int orientation;
-
- // The clockwise rotation angle relative to the orientation of the camera. This affects
- // pictures returned by the camera in Camera.PictureCallback.
- int rotation;
- if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
- orientation = (mCameraInfo.orientation + degrees) % 360;
- rotation = orientation;
- // compensate the mirror but only for orientation
- orientation = (360 - orientation) % 360;
- } else { // back-facing
- orientation = (mCameraInfo.orientation - degrees + 360) % 360;
- rotation = orientation;
- }
- mRotation = rotation;
- if (mMediaRecorder == null) {
- try {
- mCamera.setDisplayOrientation(orientation);
- final Camera.Parameters params = mCamera.getParameters();
- params.setRotation(rotation);
- mCamera.setParameters(params);
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.updateCameraOrientation", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
- }
-
- /** Sets the current camera, releasing any previously opened camera */
- private void setCamera(final Camera camera) {
- if (mCamera == camera) {
- return;
- }
-
- releaseMediaRecorder(true /* cleanupFile */);
- releaseCamera(mCamera);
- mCamera = camera;
- tryShowPreview();
- if (mListener != null) {
- mListener.onCameraChanged();
- }
- }
-
- /** Shows the preview if the camera is open and the preview is loaded */
- private void tryShowPreview() {
- if (mCameraPreview == null || mCamera == null) {
- if (mOrientationHandler != null) {
- mOrientationHandler.disable();
- mOrientationHandler = null;
- }
- releaseMediaRecorder(true /* cleanupFile */);
- mFocusOverlayManager.onPreviewStopped();
- return;
- }
- try {
- mCamera.stopPreview();
- updateCameraOrientation();
-
- final Camera.Parameters params = mCamera.getParameters();
- final Camera.Size pictureSize = chooseBestPictureSize();
- final Camera.Size previewSize = chooseBestPreviewSize(pictureSize);
- params.setPreviewSize(previewSize.width, previewSize.height);
- params.setPictureSize(pictureSize.width, pictureSize.height);
- logCameraSize("Setting preview size: ", previewSize);
- logCameraSize("Setting picture size: ", pictureSize);
- mCameraPreview.setSize(previewSize, mCameraInfo.orientation);
- for (final String focusMode : params.getSupportedFocusModes()) {
- if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
- // Use continuous focus if available
- params.setFocusMode(focusMode);
- break;
- }
- }
-
- mCamera.setParameters(params);
- mCameraPreview.startPreview(mCamera);
- mCamera.startPreview();
- mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() {
- @Override
- public void onAutoFocusMoving(final boolean start, final Camera camera) {
- mFocusOverlayManager.onAutoFocusMoving(start);
- }
- });
- mFocusOverlayManager.setParameters(mCamera.getParameters());
- mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK);
- mFocusOverlayManager.onPreviewStarted();
- tryInitOrCleanupVideoMode();
- if (mOrientationHandler == null) {
- mOrientationHandler = new OrientationHandler(mCameraPreview.getContext());
- mOrientationHandler.enable();
- }
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.tryShowPreview", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryShowPreview", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
- }
- }
- }
-
- private void tryInitOrCleanupVideoMode() {
- if (!mVideoModeRequested || mCamera == null || mCameraPreview == null) {
- releaseMediaRecorder(true /* cleanupFile */);
- return;
- }
-
- if (mMediaRecorder != null) {
- return;
- }
-
- try {
- mCamera.unlock();
- final int maxMessageSize = getMmsConfig().getMaxMessageSize();
- mMediaRecorder = new MmsVideoRecorder(mCamera, mCameraIndex, mRotation, maxMessageSize);
- mMediaRecorder.prepare();
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "FileNotFoundException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_STORAGE_FAILURE, e);
- }
- setVideoMode(false);
- return;
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
- }
- setVideoMode(false);
- return;
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
- }
- setVideoMode(false);
- return;
- }
-
- tryStartVideoCapture();
- }
-
- private void tryStartVideoCapture() {
- if (mMediaRecorder == null || mVideoCallback == null) {
- return;
- }
-
- mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
- @Override
- public void onError(final MediaRecorder mediaRecorder, final int what,
- final int extra) {
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, null);
- }
- restoreRequestedOrientation();
- }
- });
-
- mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
- @Override
- public void onInfo(final MediaRecorder mediaRecorder, final int what, final int extra) {
- if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
- what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
- stopVideo();
- }
- }
- });
-
- try {
- mMediaRecorder.start();
- final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- lockOrientation();
- } catch (final IllegalStateException e) {
- LogUtil.e(TAG, "IllegalStateException in CameraManager.tryStartVideoCapture", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
- }
- setVideoMode(false);
- restoreRequestedOrientation();
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryStartVideoCapture", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
- }
- setVideoMode(false);
- restoreRequestedOrientation();
- }
- }
-
- void stopVideo() {
- int width = ImageRequest.UNSPECIFIED_SIZE;
- int height = ImageRequest.UNSPECIFIED_SIZE;
- Uri uri = null;
- String contentType = null;
- try {
- final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
- activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- mMediaRecorder.stop();
- width = mMediaRecorder.getVideoWidth();
- height = mMediaRecorder.getVideoHeight();
- uri = mMediaRecorder.getVideoUri();
- contentType = mMediaRecorder.getContentType();
- } catch (final RuntimeException e) {
- // MediaRecorder.stop will throw a RuntimeException if the video was too short, let the
- // finally clause call the callback with null uri and handle cleanup
- LogUtil.e(TAG, "RuntimeException in CameraManager.stopVideo", e);
- } finally {
- final MediaCallback videoCallback = mVideoCallback;
- mVideoCallback = null;
- releaseMediaRecorder(false /* cleanupFile */);
- if (uri == null) {
- tryInitOrCleanupVideoMode();
- }
- videoCallback.onMediaReady(uri, contentType, width, height);
- }
- }
-
- boolean isCameraAvailable() {
- return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported;
- }
-
- /**
- * External components call into this to report if hardware acceleration is supported. When
- * hardware acceleration isn't supported, we need to report an error through the listener
- * interface
- * @param isHardwareAccelerationSupported True if the preview is rendering in a hardware
- * accelerated view.
- */
- void reportHardwareAccelerationSupported(final boolean isHardwareAccelerationSupported) {
- Assert.isMainThread();
- if (mIsHardwareAccelerationSupported == isHardwareAccelerationSupported) {
- // If the value hasn't changed nothing more to do
- return;
- }
-
- mIsHardwareAccelerationSupported = isHardwareAccelerationSupported;
- if (!isHardwareAccelerationSupported) {
- LogUtil.e(TAG, "Software rendering - cannot open camera");
- if (mListener != null) {
- mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
- }
- }
- }
-
- /** Returns the scale factor to scale the width/height to max allowed in MmsConfig */
- private float getScaleFactorForMaxAllowedSize(final int width, final int height,
- final int maxWidth, final int maxHeight) {
- if (maxWidth <= 0 || maxHeight <= 0) {
- // MmsConfig initialization runs asynchronously on application startup, so there's a
- // chance (albeit a very slight one) that we don't have it yet.
- LogUtil.w(LogUtil.BUGLE_TAG, "Max image size not loaded in MmsConfig");
- return 1.0f;
- }
-
- if (width <= maxWidth && height <= maxHeight) {
- // Already meeting requirements.
- return 1.0f;
- }
-
- return Math.min(maxWidth * 1.0f / width, maxHeight * 1.0f / height);
- }
-
- private MmsConfig getMmsConfig() {
- final int subId = mSubscriptionDataProvider != null ?
- mSubscriptionDataProvider.getConversationSelfSubId() :
- ParticipantData.DEFAULT_SELF_SUB_ID;
- return MmsConfig.get(subId);
- }
-
- /**
- * Choose the best picture size by trying to find a size close to the MmsConfig's max size,
- * which is closest to the screen aspect ratio
- */
- private Camera.Size chooseBestPictureSize() {
- final Context context = mCameraPreview.getContext();
- final Resources resources = context.getResources();
- final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
- final int displayOrientation = resources.getConfiguration().orientation;
- int cameraOrientation = mCameraInfo.orientation;
-
- int screenWidth;
- int screenHeight;
- if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE) {
- // Rotate the camera orientation 90 degrees to compensate for the rotated display
- // metrics. Direction doesn't matter because we're just using it for width/height
- cameraOrientation += 90;
- }
-
- // Check the camera orientation relative to the display.
- // For 0, 180, 360, the screen width/height are the display width/height
- // For 90, 270, the screen width/height are inverted from the display
- if (cameraOrientation % 180 == 0) {
- screenWidth = displayMetrics.widthPixels;
- screenHeight = displayMetrics.heightPixels;
- } else {
- screenWidth = displayMetrics.heightPixels;
- screenHeight = displayMetrics.widthPixels;
- }
-
- final MmsConfig mmsConfig = getMmsConfig();
- final int maxWidth = mmsConfig.getMaxImageWidth();
- final int maxHeight = mmsConfig.getMaxImageHeight();
-
- // Constrain the size within the max width/height defined by MmsConfig.
- final float scaleFactor = getScaleFactorForMaxAllowedSize(screenWidth, screenHeight,
- maxWidth, maxHeight);
- screenWidth *= scaleFactor;
- screenHeight *= scaleFactor;
-
- final float aspectRatio = BugleGservices.get().getFloat(
- BugleGservicesKeys.CAMERA_ASPECT_RATIO,
- screenWidth / (float) screenHeight);
- final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
- mCamera.getParameters().getSupportedPictureSizes());
- final int maxPixels = maxWidth * maxHeight;
-
- // Sort the sizes so the best size is first
- Collections.sort(sizes, new SizeComparator(maxWidth, maxHeight, aspectRatio, maxPixels));
-
- return sizes.get(0);
- }
-
- /**
- * Chose the best preview size based on the picture size. Try to find a size with the same
- * aspect ratio and size as the picture if possible
- */
- private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) {
- final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
- mCamera.getParameters().getSupportedPreviewSizes());
- final float aspectRatio = pictureSize.width / (float) pictureSize.height;
- final int capturePixels = pictureSize.width * pictureSize.height;
-
- // Sort the sizes so the best size is first
- Collections.sort(sizes, new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE,
- aspectRatio, capturePixels));
-
- return sizes.get(0);
- }
-
- private class OrientationHandler extends OrientationEventListener {
- OrientationHandler(final Context context) {
- super(context);
- }
-
- @Override
- public void onOrientationChanged(final int orientation) {
- updateCameraOrientation();
- }
- }
-
- private static class SizeComparator implements Comparator<Camera.Size> {
- private static final int PREFER_LEFT = -1;
- private static final int PREFER_RIGHT = 1;
-
- // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit
- private final int mMaxWidth;
- private final int mMaxHeight;
-
- // The desired aspect ratio
- private final float mTargetAspectRatio;
-
- // The desired size (width x height) to try to match
- private final int mTargetPixels;
-
- public SizeComparator(final int maxWidth, final int maxHeight,
- final float targetAspectRatio, final int targetPixels) {
- mMaxWidth = maxWidth;
- mMaxHeight = maxHeight;
- mTargetAspectRatio = targetAspectRatio;
- mTargetPixels = targetPixels;
- }
-
- /**
- * Returns a negative value if left is a better choice than right, or a positive value if
- * right is a better choice is better than left. 0 if they are equal
- */
- @Override
- public int compare(final Camera.Size left, final Camera.Size right) {
- // If one size is less than the max size prefer it over the other
- if ((left.width <= mMaxWidth && left.height <= mMaxHeight) !=
- (right.width <= mMaxWidth && right.height <= mMaxHeight)) {
- return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT;
- }
-
- // If one is closer to the target aspect ratio, prefer it.
- final float leftAspectRatio = left.width / (float) left.height;
- final float rightAspectRatio = right.width / (float) right.height;
- final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio);
- final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio);
- if (leftAspectRatioDiff != rightAspectRatioDiff) {
- return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ?
- PREFER_LEFT : PREFER_RIGHT;
- }
-
- // At this point they have the same aspect ratio diff and are either both bigger
- // than the max size or both smaller than the max size, so prefer the one closest
- // to target size
- final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels);
- final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels);
- return leftDiff - rightDiff;
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public void autoFocus() {
- if (mCamera == null) {
- return;
- }
-
- try {
- mCamera.autoFocus(new Camera.AutoFocusCallback() {
- @Override
- public void onAutoFocus(final boolean success, final Camera camera) {
- mFocusOverlayManager.onAutoFocus(success, false /* shutterDown */);
- }
- });
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.autoFocus", e);
- // If autofocus fails, the camera should have called the callback with success=false,
- // but some throw an exception here
- mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/);
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public void cancelAutoFocus() {
- if (mCamera == null) {
- return;
- }
- try {
- mCamera.cancelAutoFocus();
- } catch (final RuntimeException e) {
- // Ignore
- LogUtil.e(TAG, "RuntimeException in CameraManager.cancelAutoFocus", e);
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public boolean capture() {
- return false;
- }
-
- @Override // From FocusOverlayManager.Listener
- public void setFocusParameters() {
- if (mCamera == null) {
- return;
- }
- try {
- final Camera.Parameters parameters = mCamera.getParameters();
- parameters.setFocusMode(mFocusOverlayManager.getFocusMode());
- if (parameters.getMaxNumFocusAreas() > 0) {
- // Don't set focus areas (even to null) if focus areas aren't supported, camera may
- // crash
- parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas());
- }
- parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas());
- mCamera.setParameters(parameters);
- } catch (final RuntimeException e) {
- // This occurs when the device is out of space or when the camera is locked
- LogUtil.e(TAG, "RuntimeException in CameraManager setFocusParameters");
- }
- }
-
- private void logCameraSize(final String prefix, final Camera.Size size) {
- // Log the camera size and aspect ratio for help when examining bug reports for camera
- // failures
- LogUtil.i(TAG, prefix + size.width + "x" + size.height +
- " (" + (size.width / (float) size.height) + ")");
- }
-
-
- private Integer mSavedOrientation = null;
-
- private void lockOrientation() {
- // when we start recording, lock our orientation
- final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
- final WindowManager windowManager =
- (WindowManager) a.getSystemService(Context.WINDOW_SERVICE);
- final int rotation = windowManager.getDefaultDisplay().getRotation();
-
- mSavedOrientation = a.getRequestedOrientation();
- switch (rotation) {
- case Surface.ROTATION_0:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- break;
- case Surface.ROTATION_90:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- break;
- case Surface.ROTATION_180:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
- break;
- case Surface.ROTATION_270:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- break;
- }
-
- }
-
- private void restoreRequestedOrientation() {
- if (mSavedOrientation != null) {
- final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
- if (a != null) {
- a.setRequestedOrientation(mSavedOrientation);
- }
- mSavedOrientation = null;
- }
- }
-
- static boolean hasCameraPermission() {
- return OsUtil.hasPermission(Manifest.permission.CAMERA);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
deleted file mode 100644
index 2c7a7f2..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.widget.Chronometer;
-import android.widget.ImageButton;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.ui.mediapicker.CameraManager.MediaCallback;
-import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Chooser which allows the user to take pictures or video without leaving the current app/activity
- */
-class CameraMediaChooser extends MediaChooser implements
- CameraManager.CameraManagerListener {
- private CameraPreview.CameraPreviewHost mCameraPreviewHost;
- private ImageButton mFullScreenButton;
- private ImageButton mSwapCameraButton;
- private ImageButton mSwapModeButton;
- private ImageButton mCaptureButton;
- private ImageButton mCancelVideoButton;
- private Chronometer mVideoCounter;
- private boolean mVideoCancelled;
- private int mErrorToast;
- private View mEnabledView;
- private View mMissingPermissionView;
-
- CameraMediaChooser(final MediaPicker mediaPicker) {
- super(mediaPicker);
- }
-
- @Override
- public int getSupportedMediaTypes() {
- if (CameraManager.get().hasAnyCamera()) {
- return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
- } else {
- return MediaPicker.MEDIA_TYPE_NONE;
- }
- }
-
- @Override
- public View destroyView() {
- CameraManager.get().closeCamera();
- CameraManager.get().setListener(null);
- CameraManager.get().setSubscriptionDataProvider(null);
- return super.destroyView();
- }
-
- @Override
- protected View createView(final ViewGroup container) {
- CameraManager.get().setListener(this);
- CameraManager.get().setSubscriptionDataProvider(this);
- CameraManager.get().setVideoMode(false);
- final LayoutInflater inflater = getLayoutInflater();
- final CameraMediaChooserView view = (CameraMediaChooserView) inflater.inflate(
- R.layout.mediapicker_camera_chooser,
- container /* root */,
- false /* attachToRoot */);
- mCameraPreviewHost = (CameraPreview.CameraPreviewHost) view.findViewById(
- R.id.camera_preview);
- mCameraPreviewHost.getView().setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(final View view, final MotionEvent motionEvent) {
- if (CameraManager.get().isVideoMode()) {
- // Prevent the swipe down in video mode because video is always captured in
- // full screen
- return true;
- }
-
- return false;
- }
- });
-
- final View shutterVisual = view.findViewById(R.id.camera_shutter_visual);
-
- mFullScreenButton = (ImageButton) view.findViewById(R.id.camera_fullScreen_button);
- mFullScreenButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- mMediaPicker.setFullScreen(true);
- }
- });
-
- mSwapCameraButton = (ImageButton) view.findViewById(R.id.camera_swapCamera_button);
- mSwapCameraButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- CameraManager.get().swapCamera();
- }
- });
-
- mCaptureButton = (ImageButton) view.findViewById(R.id.camera_capture_button);
- mCaptureButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View v) {
- final float heightPercent = Math.min(mMediaPicker.getViewPager().getHeight() /
- (float) mCameraPreviewHost.getView().getHeight(), 1);
-
- if (CameraManager.get().isRecording()) {
- CameraManager.get().stopVideo();
- } else {
- final CameraManager.MediaCallback callback = new CameraManager.MediaCallback() {
- @Override
- public void onMediaReady(
- final Uri uriToVideo, final String contentType,
- final int width, final int height) {
- mVideoCounter.stop();
- if (mVideoCancelled || uriToVideo == null) {
- mVideoCancelled = false;
- } else {
- final Rect startRect = new Rect();
- // It's possible to throw out the chooser while taking the
- // picture/video. In that case, still use the attachment, just
- // skip the startRect
- if (mView != null) {
- mView.getGlobalVisibleRect(startRect);
- }
- mMediaPicker.dispatchItemsSelected(
- new MediaPickerMessagePartData(startRect, contentType,
- uriToVideo, width, height),
- true /* dismissMediaPicker */);
- }
- updateViewState();
- }
-
- @Override
- public void onMediaFailed(final Exception exception) {
- UiUtils.showToastAtBottom(R.string.camera_media_failure);
- updateViewState();
- }
-
- @Override
- public void onMediaInfo(final int what) {
- if (what == MediaCallback.MEDIA_NO_DATA) {
- UiUtils.showToastAtBottom(R.string.camera_media_failure);
- }
- updateViewState();
- }
- };
- if (CameraManager.get().isVideoMode()) {
- CameraManager.get().startVideo(callback);
- mVideoCounter.setBase(SystemClock.elapsedRealtime());
- mVideoCounter.start();
- updateViewState();
- } else {
- showShutterEffect(shutterVisual);
- CameraManager.get().takePicture(heightPercent, callback);
- updateViewState();
- }
- }
- }
- });
-
- mSwapModeButton = (ImageButton) view.findViewById(R.id.camera_swap_mode_button);
- mSwapModeButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- final boolean isSwitchingToVideo = !CameraManager.get().isVideoMode();
- if (isSwitchingToVideo && !OsUtil.hasRecordAudioPermission()) {
- requestRecordAudioPermission();
- } else {
- onSwapMode();
- }
- }
- });
-
- mCancelVideoButton = (ImageButton) view.findViewById(R.id.camera_cancel_button);
- mCancelVideoButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- mVideoCancelled = true;
- CameraManager.get().stopVideo();
- mMediaPicker.dismiss(true);
- }
- });
-
- mVideoCounter = (Chronometer) view.findViewById(R.id.camera_video_counter);
-
- CameraManager.get().setRenderOverlay((RenderOverlay) view.findViewById(R.id.focus_visual));
-
- mEnabledView = view.findViewById(R.id.mediapicker_enabled);
- mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
-
- // Must set mView before calling updateViewState because it operates on mView
- mView = view;
- updateViewState();
- updateForPermissionState(CameraManager.hasCameraPermission());
- return view;
- }
-
- @Override
- public int getIconResource() {
- return R.drawable.ic_camera_light;
- }
-
- @Override
- public int getIconDescriptionResource() {
- return R.string.mediapicker_cameraChooserDescription;
- }
-
- /**
- * Updates the view when entering or leaving full-screen camera mode
- * @param fullScreen
- */
- @Override
- void onFullScreenChanged(final boolean fullScreen) {
- super.onFullScreenChanged(fullScreen);
- if (!fullScreen && CameraManager.get().isVideoMode()) {
- CameraManager.get().setVideoMode(false);
- }
- updateViewState();
- }
-
- /**
- * Initializes the control to a default state when it is opened / closed
- * @param open True if the control is opened
- */
- @Override
- void onOpenedChanged(final boolean open) {
- super.onOpenedChanged(open);
- updateViewState();
- }
-
- @Override
- protected void setSelected(final boolean selected) {
- super.setSelected(selected);
- if (selected) {
- if (CameraManager.hasCameraPermission()) {
- // If an error occurred before the chooser was selected, show it now
- showErrorToastIfNeeded();
- } else {
- requestCameraPermission();
- }
- }
- }
-
- private void requestCameraPermission() {
- mMediaPicker.requestPermissions(new String[] { Manifest.permission.CAMERA },
- MediaPicker.CAMERA_PERMISSION_REQUEST_CODE);
- }
-
- private void requestRecordAudioPermission() {
- mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
- MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
- }
-
- @Override
- protected void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (requestCode == MediaPicker.CAMERA_PERMISSION_REQUEST_CODE) {
- final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
- updateForPermissionState(permissionGranted);
- if (permissionGranted) {
- mCameraPreviewHost.onCameraPermissionGranted();
- }
- } else if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
- Assert.isFalse(CameraManager.get().isVideoMode());
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // Switch to video mode
- onSwapMode();
- } else {
- // Stay in still-photo mode
- }
- }
- }
-
- private void updateForPermissionState(final boolean granted) {
- // onRequestPermissionsResult can sometimes get called before createView().
- if (mEnabledView == null) {
- return;
- }
-
- mEnabledView.setVisibility(granted ? View.VISIBLE : View.GONE);
- mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
- }
-
- @Override
- public boolean canSwipeDown() {
- if (CameraManager.get().isVideoMode()) {
- return true;
- }
- return super.canSwipeDown();
- }
-
- /**
- * Handles an error from the camera manager by showing the appropriate error message to the user
- * @param errorCode One of the CameraManager.ERROR_* constants
- * @param e The exception which caused the error, if any
- */
- @Override
- public void onCameraError(final int errorCode, final Exception e) {
- switch (errorCode) {
- case CameraManager.ERROR_OPENING_CAMERA:
- case CameraManager.ERROR_SHOWING_PREVIEW:
- mErrorToast = R.string.camera_error_opening;
- break;
- case CameraManager.ERROR_INITIALIZING_VIDEO:
- mErrorToast = R.string.camera_error_video_init_fail;
- updateViewState();
- break;
- case CameraManager.ERROR_STORAGE_FAILURE:
- mErrorToast = R.string.camera_error_storage_fail;
- updateViewState();
- break;
- case CameraManager.ERROR_TAKING_PICTURE:
- mErrorToast = R.string.camera_error_failure_taking_picture;
- break;
- default:
- mErrorToast = R.string.camera_error_unknown;
- LogUtil.w(LogUtil.BUGLE_TAG, "Unknown camera error:" + errorCode);
- break;
- }
- showErrorToastIfNeeded();
- }
-
- private void showErrorToastIfNeeded() {
- if (mErrorToast != 0 && mSelected) {
- UiUtils.showToastAtBottom(mErrorToast);
- mErrorToast = 0;
- }
- }
-
- @Override
- public void onCameraChanged() {
- updateViewState();
- }
-
- private void onSwapMode() {
- CameraManager.get().setVideoMode(!CameraManager.get().isVideoMode());
- if (CameraManager.get().isVideoMode()) {
- mMediaPicker.setFullScreen(true);
-
- // For now we start recording immediately
- mCaptureButton.performClick();
- }
- updateViewState();
- }
-
- private void showShutterEffect(final View shutterVisual) {
- final float maxAlpha = getContext().getResources().getFraction(
- R.fraction.camera_shutter_max_alpha, 1 /* base */, 1 /* pBase */);
-
- // Divide by 2 so each half of the animation adds up to the full duration
- final int animationDuration = getContext().getResources().getInteger(
- R.integer.camera_shutter_duration) / 2;
-
- final AnimationSet animation = new AnimationSet(false /* shareInterpolator */);
- final Animation alphaInAnimation = new AlphaAnimation(0.0f, maxAlpha);
- alphaInAnimation.setDuration(animationDuration);
- animation.addAnimation(alphaInAnimation);
-
- final Animation alphaOutAnimation = new AlphaAnimation(maxAlpha, 0.0f);
- alphaOutAnimation.setStartOffset(animationDuration);
- alphaOutAnimation.setDuration(animationDuration);
- animation.addAnimation(alphaOutAnimation);
-
- animation.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(final Animation animation) {
- shutterVisual.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(final Animation animation) {
- shutterVisual.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationRepeat(final Animation animation) {
- }
- });
- shutterVisual.startAnimation(animation);
- }
-
- /** Updates the state of the buttons and overlays based on the current state of the view */
- private void updateViewState() {
- if (mView == null) {
- return;
- }
-
- final Context context = getContext();
- if (context == null) {
- // Context is null if the fragment was already removed from the activity
- return;
- }
- final boolean fullScreen = mMediaPicker.isFullScreen();
- final boolean videoMode = CameraManager.get().isVideoMode();
- final boolean isRecording = CameraManager.get().isRecording();
- final boolean isCameraAvailable = isCameraAvailable();
- final Camera.CameraInfo cameraInfo = CameraManager.get().getCameraInfo();
- final boolean frontCamera = cameraInfo != null && cameraInfo.facing ==
- Camera.CameraInfo.CAMERA_FACING_FRONT;
-
- mView.setSystemUiVisibility(
- fullScreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE :
- View.SYSTEM_UI_FLAG_VISIBLE);
-
- mFullScreenButton.setVisibility(!fullScreen ? View.VISIBLE : View.GONE);
- mFullScreenButton.setEnabled(isCameraAvailable);
- mSwapCameraButton.setVisibility(
- fullScreen && !isRecording && CameraManager.get().hasFrontAndBackCamera() ?
- View.VISIBLE : View.GONE);
- mSwapCameraButton.setImageResource(frontCamera ?
- R.drawable.ic_camera_front_light :
- R.drawable.ic_camera_rear_light);
- mSwapCameraButton.setEnabled(isCameraAvailable);
-
- mCancelVideoButton.setVisibility(isRecording ? View.VISIBLE : View.GONE);
- mVideoCounter.setVisibility(isRecording ? View.VISIBLE : View.GONE);
-
- mSwapModeButton.setImageResource(videoMode ?
- R.drawable.ic_mp_camera_small_light :
- R.drawable.ic_mp_video_small_light);
- mSwapModeButton.setContentDescription(context.getString(videoMode ?
- R.string.camera_switch_to_still_mode : R.string.camera_switch_to_video_mode));
- mSwapModeButton.setVisibility(isRecording ? View.GONE : View.VISIBLE);
- mSwapModeButton.setEnabled(isCameraAvailable);
-
- if (isRecording) {
- mCaptureButton.setImageResource(R.drawable.ic_mp_capture_stop_large_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_stop_recording));
- } else if (videoMode) {
- mCaptureButton.setImageResource(R.drawable.ic_mp_video_large_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_start_recording));
- } else {
- mCaptureButton.setImageResource(R.drawable.ic_checkmark_large_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_take_picture));
- }
- mCaptureButton.setEnabled(isCameraAvailable);
- }
-
- @Override
- int getActionBarTitleResId() {
- return 0;
- }
-
- /**
- * Returns if the camera is currently ready camera is loaded and not taking a picture.
- * otherwise we should avoid taking another picture, swapping camera or recording video.
- */
- private boolean isCameraAvailable() {
- return CameraManager.get().isCameraAvailable();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java
deleted file mode 100644
index 64c07b2..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.hardware.Camera;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import com.android.messaging.R;
-import com.android.messaging.ui.PersistentInstanceState;
-import com.android.messaging.util.ThreadUtil;
-
-public class CameraMediaChooserView extends FrameLayout implements PersistentInstanceState {
- private static final String KEY_CAMERA_INDEX = "camera_index";
-
- // True if we have at least queued an update to the view tree to support software rendering
- // fallback
- private boolean mIsSoftwareFallbackActive;
-
- public CameraMediaChooserView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- final Bundle bundle = new Bundle();
- bundle.putInt(KEY_CAMERA_INDEX, CameraManager.get().getCameraIndex());
- return bundle;
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- if (!(state instanceof Bundle)) {
- return;
- }
-
- final Bundle bundle = (Bundle) state;
- CameraManager.get().selectCameraByIndex(bundle.getInt(KEY_CAMERA_INDEX));
- }
-
- @Override
- public Parcelable saveState() {
- return onSaveInstanceState();
- }
-
- @Override
- public void restoreState(final Parcelable restoredState) {
- onRestoreInstanceState(restoredState);
- }
-
- @Override
- public void resetState() {
- CameraManager.get().selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- super.onDraw(canvas);
- // If the canvas isn't hardware accelerated, we have to replace the HardwareCameraPreview
- // with a SoftwareCameraPreview which supports software rendering
- if (!canvas.isHardwareAccelerated() && !mIsSoftwareFallbackActive) {
- mIsSoftwareFallbackActive = true;
- // Post modifying the tree since we can't modify the view tree during a draw pass
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- final HardwareCameraPreview cameraPreview =
- (HardwareCameraPreview) findViewById(R.id.camera_preview);
- if (cameraPreview == null) {
- return;
- }
- final ViewGroup parent = ((ViewGroup) cameraPreview.getParent());
- final int index = parent.indexOfChild(cameraPreview);
- final SoftwareCameraPreview softwareCameraPreview =
- new SoftwareCameraPreview(getContext());
- // Be sure to remove the hardware view before adding the software view to
- // prevent having 2 camera previews active at the same time
- parent.removeView(cameraPreview);
- parent.addView(softwareCameraPreview, index);
- }
- });
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraPreview.java b/src/com/android/messaging/ui/mediapicker/CameraPreview.java
deleted file mode 100644
index ecac978..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraPreview.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.hardware.Camera;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import com.android.messaging.util.Assert;
-
-import java.io.IOException;
-
-/**
- * Contains shared code for SoftwareCameraPreview and HardwareCameraPreview, cannot use inheritance
- * because those classes must inherit from separate Views, so those classes delegate calls to this
- * helper class. Specifics for each implementation are in CameraPreviewHost
- */
-public class CameraPreview {
- public interface CameraPreviewHost {
- View getView();
- boolean isValid();
- void startPreview(final Camera camera) throws IOException;
- void onCameraPermissionGranted();
-
- }
-
- private int mCameraWidth = -1;
- private int mCameraHeight = -1;
-
- private final CameraPreviewHost mHost;
-
- public CameraPreview(final CameraPreviewHost host) {
- Assert.notNull(host);
- Assert.notNull(host.getView());
- mHost = host;
- }
-
- public void setSize(final Camera.Size size, final int orientation) {
- switch (orientation) {
- case 0:
- case 180:
- mCameraWidth = size.width;
- mCameraHeight = size.height;
- break;
- case 90:
- case 270:
- default:
- mCameraWidth = size.height;
- mCameraHeight = size.width;
- }
- mHost.getView().requestLayout();
- }
-
- public int getWidthMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mCameraHeight >= 0) {
- final int width = View.MeasureSpec.getSize(widthMeasureSpec);
- return MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
- } else {
- return widthMeasureSpec;
- }
- }
-
- public int getHeightMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mCameraHeight >= 0) {
- final int orientation = getContext().getResources().getConfiguration().orientation;
- final int width = View.MeasureSpec.getSize(widthMeasureSpec);
- final float aspectRatio = (float) mCameraWidth / (float) mCameraHeight;
- int height;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- height = (int) (width * aspectRatio);
- } else {
- height = (int) (width / aspectRatio);
- }
- return View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
- } else {
- return heightMeasureSpec;
- }
- }
-
- public void onVisibilityChanged(final int visibility) {
- if (CameraManager.hasCameraPermission()) {
- if (visibility == View.VISIBLE) {
- CameraManager.get().openCamera();
- } else {
- CameraManager.get().closeCamera();
- }
- }
- }
-
- public Context getContext() {
- return mHost.getView().getContext();
- }
-
- public void setOnTouchListener(final View.OnTouchListener listener) {
- mHost.getView().setOnTouchListener(listener);
- }
-
- public int getHeight() {
- return mHost.getView().getHeight();
- }
-
- public void onAttachedToWindow() {
- if (CameraManager.hasCameraPermission()) {
- CameraManager.get().openCamera();
- }
- }
-
- public void onDetachedFromWindow() {
- CameraManager.get().closeCamera();
- }
-
- public void onRestoreInstanceState() {
- if (CameraManager.hasCameraPermission()) {
- CameraManager.get().openCamera();
- }
- }
-
- public void onCameraPermissionGranted() {
- CameraManager.get().openCamera();
- }
-
- /**
- * @return True if the view is valid and prepared for the camera to start showing the preview
- */
- public boolean isValid() {
- return mHost.isValid();
- }
-
- /**
- * Starts the camera preview on the current surface. Abstracts out the differences in API
- * from the CameraManager
- * @throws IOException Which is caught by the CameraManager to display an error
- */
- public void startPreview(final Camera camera) throws IOException {
- mHost.startPreview(camera);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
deleted file mode 100644
index 2c36752..0000000
--- a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.mediapicker;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.SafeAsyncTask;
-
-/**
- * Wraps around the functionalities to allow the user to pick images from the document
- * picker. Instances of this class must be tied to a Fragment which is able to delegate activity
- * result callbacks.
- */
-public class DocumentImagePicker {
-
- /**
- * An interface for a listener that listens for when a document has been picked.
- */
- public interface SelectionListener {
- /**
- * Called when an document is selected from picker. At this point, the file hasn't been
- * actually loaded and staged in the temp directory, so we are passing in a pending
- * MessagePartData, which the consumer should use to display a placeholder image.
- * @param pendingItem a temporary attachment data for showing the placeholder state.
- */
- void onDocumentSelected(PendingAttachmentData pendingItem);
- }
-
- // The owning fragment.
- private final Fragment mFragment;
-
- // The listener on the picker events.
- private final SelectionListener mListener;
-
- private static final String EXTRA_PHOTO_URL = "photo_url";
-
- /**
- * Creates a new instance of DocumentImagePicker.
- * @param activity The activity that owns the picker, or the activity that hosts the owning
- * fragment.
- */
- public DocumentImagePicker(final Fragment fragment,
- final SelectionListener listener) {
- mFragment = fragment;
- mListener = listener;
- }
-
- /**
- * Intent out to open an image/video from document picker.
- */
- public void launchPicker() {
- UIIntents.get().launchDocumentImagePicker(mFragment);
- }
-
- /**
- * Must be called from the fragment/activity's onActivityResult().
- */
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- if (requestCode == UIIntents.REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER &&
- resultCode == Activity.RESULT_OK) {
- // Sometimes called after media item has been picked from the document picker.
- String url = data.getStringExtra(EXTRA_PHOTO_URL);
- if (url == null) {
- // we're using the builtin photo picker which supplies the return
- // url as it's "data"
- url = data.getDataString();
- if (url == null) {
- final Bundle extras = data.getExtras();
- if (extras != null) {
- final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
- if (uri != null) {
- url = uri.toString();
- }
- }
- }
- }
-
- // Guard against null uri cases for when the activity returns a null/invalid intent.
- if (url != null) {
- final Uri uri = Uri.parse(url);
- prepareDocumentForAttachment(uri);
- }
- }
- }
-
- private void prepareDocumentForAttachment(final Uri documentUri) {
- // Notify our listener with a PendingAttachmentData containing the metadata.
- // Asynchronously get the content type for the picked image since
- // ImageUtils.getContentType() potentially involves I/O and can be expensive.
- new SafeAsyncTask<Void, Void, String>() {
- @Override
- protected String doInBackgroundTimed(final Void... params) {
- return ImageUtils.getContentType(
- Factory.get().getApplicationContext().getContentResolver(), documentUri);
- }
-
- @Override
- protected void onPostExecute(final String contentType) {
- // Ask the listener to create a temporary placeholder item to show the progress.
- final PendingAttachmentData pendingItem =
- PendingAttachmentData.createPendingAttachmentData(contentType,
- documentUri);
- mListener.onDocumentSelected(pendingItem);
- }
- }.executeOnThreadPool();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java b/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java
deleted file mode 100644
index fda3b19..0000000
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CursorAdapter;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.mediapicker.GalleryGridItemView.HostInterface;
-import com.android.messaging.util.Assert;
-
-/**
- * Bridges between the image cursor loaded by GalleryBoundCursorLoader and the GalleryGridView.
- */
-public class GalleryGridAdapter extends CursorAdapter {
- private GalleryGridItemView.HostInterface mGgivHostInterface;
-
- public GalleryGridAdapter(final Context context, final Cursor cursor) {
- super(context, cursor, 0);
- }
-
- public void setHostInterface(final HostInterface ggivHostInterface) {
- mGgivHostInterface = ggivHostInterface;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void bindView(final View view, final Context context, final Cursor cursor) {
- Assert.isTrue(view instanceof GalleryGridItemView);
- final GalleryGridItemView galleryImageView = (GalleryGridItemView) view;
- galleryImageView.bind(cursor, mGgivHostInterface);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
- final LayoutInflater layoutInflater = LayoutInflater.from(context);
- return layoutInflater.inflate(R.layout.gallery_grid_item_view, parent, false);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
deleted file mode 100644
index 3d71fe6..0000000
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.FrameLayout;
-import android.widget.ImageView.ScaleType;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.ui.AsyncImageView;
-import com.android.messaging.ui.ConversationDrawables;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Shows an item in the gallery picker grid view. Hosts an FileImageView with a checkbox.
- */
-public class GalleryGridItemView extends FrameLayout {
- /**
- * Implemented by the owner of this GalleryGridItemView instance to communicate on media
- * picking and selection events.
- */
- public interface HostInterface {
- void onItemClicked(View view, GalleryGridItemData data, boolean longClick);
- boolean isItemSelected(GalleryGridItemData data);
- boolean isMultiSelectEnabled();
- }
-
- @VisibleForTesting
- GalleryGridItemData mData;
- private AsyncImageView mImageView;
- private CheckBox mCheckBox;
- private HostInterface mHostInterface;
- private final OnClickListener mOnClickListener = new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/);
- }
- };
-
- public GalleryGridItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = DataModel.get().createGalleryGridItemData();
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mImageView = (AsyncImageView) findViewById(R.id.image);
- mCheckBox = (CheckBox) findViewById(R.id.checkbox);
- mCheckBox.setOnClickListener(mOnClickListener);
- setOnClickListener(mOnClickListener);
- final OnLongClickListener longClickListener = new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View v) {
- mHostInterface.onItemClicked(v, mData, true /* longClick */);
- return true;
- }
- };
- setOnLongClickListener(longClickListener);
- mCheckBox.setOnLongClickListener(longClickListener);
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- // Enlarge the clickable region for the checkbox to fill the entire view.
- final Rect region = new Rect(0, 0, getWidth(), getHeight());
- setTouchDelegate(new TouchDelegate(region, mCheckBox) {
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- setPressed(true);
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- setPressed(false);
- break;
- }
- return super.onTouchEvent(event);
- }
- });
- }
- });
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- // The grid view auto-fit the columns, so we want to let the height match the width
- // to make the image square.
- super.onMeasure(widthMeasureSpec, widthMeasureSpec);
- }
-
- public void bind(final Cursor cursor, final HostInterface hostInterface) {
- final int desiredSize = getResources()
- .getDimensionPixelSize(R.dimen.gallery_image_cell_size);
- mData.bind(cursor, desiredSize, desiredSize);
- mHostInterface = hostInterface;
- updateViewState();
- }
-
- private void updateViewState() {
- updateImageView();
- if (mHostInterface.isMultiSelectEnabled() && !mData.isDocumentPickerItem()) {
- mCheckBox.setVisibility(VISIBLE);
- mCheckBox.setClickable(true);
- mCheckBox.setChecked(mHostInterface.isItemSelected(mData));
- } else {
- mCheckBox.setVisibility(GONE);
- mCheckBox.setClickable(false);
- }
- }
-
- private void updateImageView() {
- if (mData.isDocumentPickerItem()) {
- mImageView.setScaleType(ScaleType.CENTER);
- setBackgroundColor(ConversationDrawables.get().getConversationThemeColor());
- mImageView.setImageResourceId(null);
- mImageView.setImageResource(R.drawable.ic_photo_library_light);
- mImageView.setContentDescription(getResources().getString(
- R.string.pick_image_from_document_library_content_description));
- } else {
- mImageView.setScaleType(ScaleType.CENTER_CROP);
- setBackgroundColor(getResources().getColor(R.color.gallery_image_default_background));
- mImageView.setImageResourceId(mData.getImageRequestDescriptor());
- final long dateSeconds = mData.getDateSeconds();
- final boolean isValidDate = (dateSeconds > 0);
- final int templateId = isValidDate ?
- R.string.mediapicker_gallery_image_item_description :
- R.string.mediapicker_gallery_image_item_description_no_date;
- String contentDescription = String.format(getResources().getString(templateId),
- dateSeconds * TimeUnit.SECONDS.toMillis(1));
- mImageView.setContentDescription(contentDescription);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
deleted file mode 100644
index a5a7dad..0000000
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.v4.util.ArrayMap;
-import android.util.AttributeSet;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
-import com.android.messaging.ui.PersistentInstanceState;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Shows a list of galley images from external storage in a GridView with multi-select
- * capabilities, and with the option to intent out to a standalone image picker.
- */
-public class GalleryGridView extends MediaPickerGridView implements
- GalleryGridItemView.HostInterface,
- PersistentInstanceState,
- DraftMessageDataListener {
- /**
- * Implemented by the owner of this GalleryGridView instance to communicate on image
- * picking and multi-image selection events.
- */
- public interface GalleryGridViewListener {
- void onDocumentPickerItemClicked();
- void onItemSelected(MessagePartData item);
- void onItemUnselected(MessagePartData item);
- void onConfirmSelection();
- void onUpdate();
- }
-
- private GalleryGridViewListener mListener;
-
- // TODO: Consider putting this into the data model object if we add more states.
- private final ArrayMap<Uri, MessagePartData> mSelectedImages;
- private boolean mIsMultiSelectMode = false;
- private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
-
- public GalleryGridView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mSelectedImages = new ArrayMap<Uri, MessagePartData>();
- }
-
- public void setHostInterface(final GalleryGridViewListener hostInterface) {
- mListener = hostInterface;
- }
-
- public void setDraftMessageDataModel(final BindingBase<DraftMessageData> dataModel) {
- mDraftMessageDataModel = BindingBase.createBindingReference(dataModel);
- mDraftMessageDataModel.getData().addListener(this);
- }
-
- @Override
- public void onItemClicked(final View view, final GalleryGridItemData data,
- final boolean longClick) {
- if (data.isDocumentPickerItem()) {
- mListener.onDocumentPickerItemClicked();
- } else if (ContentType.isMediaType(data.getContentType())) {
- if (longClick) {
- // Turn on multi-select mode when an item is long-pressed.
- setMultiSelectEnabled(true);
- }
-
- final Rect startRect = new Rect();
- view.getGlobalVisibleRect(startRect);
- if (isMultiSelectEnabled()) {
- toggleItemSelection(startRect, data);
- } else {
- mListener.onItemSelected(data.constructMessagePartData(startRect));
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG,
- "Selected item has invalid contentType " + data.getContentType());
- }
- }
-
- @Override
- public boolean isItemSelected(final GalleryGridItemData data) {
- return mSelectedImages.containsKey(data.getImageUri());
- }
-
- int getSelectionCount() {
- return mSelectedImages.size();
- }
-
- @Override
- public boolean isMultiSelectEnabled() {
- return mIsMultiSelectMode;
- }
-
- private void toggleItemSelection(final Rect startRect, final GalleryGridItemData data) {
- Assert.isTrue(isMultiSelectEnabled());
- if (isItemSelected(data)) {
- final MessagePartData item = mSelectedImages.remove(data.getImageUri());
- mListener.onItemUnselected(item);
- if (mSelectedImages.size() == 0) {
- // No image is selected any more, turn off multi-select mode.
- setMultiSelectEnabled(false);
- }
- } else {
- final MessagePartData item = data.constructMessagePartData(startRect);
- mSelectedImages.put(data.getImageUri(), item);
- mListener.onItemSelected(item);
- }
- invalidateViews();
- }
-
- private void toggleMultiSelect() {
- mIsMultiSelectMode = !mIsMultiSelectMode;
- invalidateViews();
- }
-
- private void setMultiSelectEnabled(final boolean enabled) {
- if (mIsMultiSelectMode != enabled) {
- toggleMultiSelect();
- }
- }
-
- private boolean canToggleMultiSelect() {
- // We allow the user to toggle multi-select mode only when nothing has selected. If
- // something has been selected, we show a confirm button instead.
- return mSelectedImages.size() == 0;
- }
-
- public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
- inflater.inflate(R.menu.gallery_picker_menu, menu);
- final MenuItem toggleMultiSelect = menu.findItem(R.id.action_multiselect);
- final MenuItem confirmMultiSelect = menu.findItem(R.id.action_confirm_multiselect);
- final boolean canToggleMultiSelect = canToggleMultiSelect();
- toggleMultiSelect.setVisible(canToggleMultiSelect);
- confirmMultiSelect.setVisible(!canToggleMultiSelect);
- }
-
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_multiselect:
- Assert.isTrue(canToggleMultiSelect());
- toggleMultiSelect();
- return true;
-
- case R.id.action_confirm_multiselect:
- Assert.isTrue(!canToggleMultiSelect());
- mListener.onConfirmSelection();
- return true;
- }
- return false;
- }
-
-
- @Override
- public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
- mDraftMessageDataModel.ensureBound(data);
- // Whenever attachment changed, refresh selection state to remove those that are not
- // selected.
- if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
- DraftMessageData.ATTACHMENTS_CHANGED) {
- refreshImageSelectionStateOnAttachmentChange();
- }
- }
-
- @Override
- public void onDraftAttachmentLimitReached(final DraftMessageData data) {
- mDraftMessageDataModel.ensureBound(data);
- // Whenever draft attachment limit is reach, refresh selection state to remove those
- // not actually added to draft.
- refreshImageSelectionStateOnAttachmentChange();
- }
-
- @Override
- public void onDraftAttachmentLoadFailed() {
- // Nothing to do since the failed attachment gets removed automatically.
- }
-
- private void refreshImageSelectionStateOnAttachmentChange() {
- boolean changed = false;
- final Iterator<Map.Entry<Uri, MessagePartData>> iterator =
- mSelectedImages.entrySet().iterator();
- while (iterator.hasNext()) {
- Map.Entry<Uri, MessagePartData> entry = iterator.next();
- if (!mDraftMessageDataModel.getData().containsAttachment(entry.getKey())) {
- iterator.remove();
- changed = true;
- }
- }
-
- if (changed) {
- mListener.onUpdate();
- invalidateViews();
- }
- }
-
- @Override // PersistentInstanceState
- public Parcelable saveState() {
- return onSaveInstanceState();
- }
-
- @Override // PersistentInstanceState
- public void restoreState(final Parcelable restoredState) {
- onRestoreInstanceState(restoredState);
- invalidateViews();
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- final SavedState savedState = new SavedState(superState);
- savedState.isMultiSelectMode = mIsMultiSelectMode;
- savedState.selectedImages = mSelectedImages.values()
- .toArray(new MessagePartData[mSelectedImages.size()]);
- return savedState;
- }
-
- @Override
- public void onRestoreInstanceState(final Parcelable state) {
- if (!(state instanceof SavedState)) {
- super.onRestoreInstanceState(state);
- return;
- }
-
- final SavedState savedState = (SavedState) state;
- super.onRestoreInstanceState(savedState.getSuperState());
- mIsMultiSelectMode = savedState.isMultiSelectMode;
- mSelectedImages.clear();
- for (int i = 0; i < savedState.selectedImages.length; i++) {
- final MessagePartData selectedImage = savedState.selectedImages[i];
- mSelectedImages.put(selectedImage.getContentUri(), selectedImage);
- }
- }
-
- @Override // PersistentInstanceState
- public void resetState() {
- mSelectedImages.clear();
- mIsMultiSelectMode = false;
- invalidateViews();
- }
-
- public static class SavedState extends BaseSavedState {
- boolean isMultiSelectMode;
- MessagePartData[] selectedImages;
-
- SavedState(final Parcelable superState) {
- super(superState);
- }
-
- private SavedState(final Parcel in) {
- super(in);
- isMultiSelectMode = in.readInt() == 1 ? true : false;
-
- // Read parts
- final int partCount = in.readInt();
- selectedImages = new MessagePartData[partCount];
- for (int i = 0; i < partCount; i++) {
- selectedImages[i] = ((MessagePartData) in.readParcelable(
- MessagePartData.class.getClassLoader()));
- }
- }
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(isMultiSelectMode ? 1 : 0);
-
- // Write parts
- out.writeInt(selectedImages.length);
- for (final MessagePartData image : selectedImages) {
- out.writeParcelable(image, flags);
- }
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(final Parcel in) {
- return new SavedState(in);
- }
- @Override
- public SavedState[] newArray(final int size) {
- return new SavedState[size];
- }
- };
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
deleted file mode 100644
index 9422386..0000000
--- a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.Manifest;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.support.v7.app.ActionBar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.datamodel.data.MediaPickerData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Chooser which allows the user to select one or more existing images or videos
- */
-class GalleryMediaChooser extends MediaChooser implements
- GalleryGridView.GalleryGridViewListener, MediaPickerDataListener {
- private final GalleryGridAdapter mAdapter;
- private GalleryGridView mGalleryGridView;
- private View mMissingPermissionView;
-
- GalleryMediaChooser(final MediaPicker mediaPicker) {
- super(mediaPicker);
- mAdapter = new GalleryGridAdapter(Factory.get().getApplicationContext(), null);
- }
-
- @Override
- public int getSupportedMediaTypes() {
- return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
- }
-
- @Override
- public View destroyView() {
- mGalleryGridView.setAdapter(null);
- mAdapter.setHostInterface(null);
- // The loader is started only if startMediaPickerDataLoader() is called
- if (OsUtil.hasStoragePermission()) {
- mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_IMAGE_LOADER);
- }
- return super.destroyView();
- }
-
- @Override
- public int getIconResource() {
- return R.drawable.ic_image_light;
- }
-
- @Override
- public int getIconDescriptionResource() {
- return R.string.mediapicker_galleryChooserDescription;
- }
-
- @Override
- public boolean canSwipeDown() {
- return mGalleryGridView.canSwipeDown();
- }
-
- @Override
- public void onItemSelected(final MessagePartData item) {
- mMediaPicker.dispatchItemsSelected(item, !mGalleryGridView.isMultiSelectEnabled());
- }
-
- @Override
- public void onItemUnselected(final MessagePartData item) {
- mMediaPicker.dispatchItemUnselected(item);
- }
-
- @Override
- public void onConfirmSelection() {
- // The user may only confirm if multiselect is enabled.
- Assert.isTrue(mGalleryGridView.isMultiSelectEnabled());
- mMediaPicker.dispatchConfirmItemSelection();
- }
-
- @Override
- public void onUpdate() {
- mMediaPicker.invalidateOptionsMenu();
- }
-
- @Override
- public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
- if (mView != null) {
- mGalleryGridView.onCreateOptionsMenu(inflater, menu);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- return (mView != null) ? mGalleryGridView.onOptionsItemSelected(item) : false;
- }
-
- @Override
- protected View createView(final ViewGroup container) {
- final LayoutInflater inflater = getLayoutInflater();
- final View view = inflater.inflate(
- R.layout.mediapicker_image_chooser,
- container /* root */,
- false /* attachToRoot */);
-
- mGalleryGridView = (GalleryGridView) view.findViewById(R.id.gallery_grid_view);
- mAdapter.setHostInterface(mGalleryGridView);
- mGalleryGridView.setAdapter(mAdapter);
- mGalleryGridView.setHostInterface(this);
- mGalleryGridView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel());
- if (OsUtil.hasStoragePermission()) {
- startMediaPickerDataLoader();
- }
-
- mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
- updateForPermissionState(OsUtil.hasStoragePermission());
- return view;
- }
-
- @Override
- int getActionBarTitleResId() {
- return R.string.mediapicker_gallery_title;
- }
-
- @Override
- public void onDocumentPickerItemClicked() {
- mMediaPicker.launchDocumentPicker();
- }
-
- @Override
- void updateActionBar(final ActionBar actionBar) {
- super.updateActionBar(actionBar);
- if (mGalleryGridView == null) {
- return;
- }
- final int selectionCount = mGalleryGridView.getSelectionCount();
- if (selectionCount > 0 && mGalleryGridView.isMultiSelectEnabled()) {
- actionBar.setTitle(getContext().getResources().getString(
- R.string.mediapicker_gallery_title_selection,
- selectionCount));
- }
- }
-
- @Override
- public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data,
- final int loaderId) {
- mBindingRef.ensureBound(mediaPickerData);
- Assert.equals(MediaPickerData.GALLERY_IMAGE_LOADER, loaderId);
- Cursor rawCursor = null;
- if (data instanceof Cursor) {
- rawCursor = (Cursor) data;
- }
- // Before delivering the cursor, wrap around the local gallery cursor
- // with an extra item for document picker integration in the front.
- final MatrixCursor specialItemsCursor =
- new MatrixCursor(GalleryGridItemData.SPECIAL_ITEM_COLUMNS);
- specialItemsCursor.addRow(new Object[] { GalleryGridItemData.ID_DOCUMENT_PICKER_ITEM });
- final MergeCursor cursor =
- new MergeCursor(new Cursor[] { specialItemsCursor, rawCursor });
- mAdapter.swapCursor(cursor);
- }
-
- @Override
- public void onResume() {
- if (OsUtil.hasStoragePermission()) {
- // Work around a bug in MediaStore where cursors querying the Files provider don't get
- // updated for changes to Images.Media or Video.Media.
- startMediaPickerDataLoader();
- }
- }
-
- @Override
- protected void setSelected(final boolean selected) {
- super.setSelected(selected);
- if (selected && !OsUtil.hasStoragePermission()) {
- mMediaPicker.requestPermissions(
- new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
- MediaPicker.GALLERY_PERMISSION_REQUEST_CODE);
- }
- }
-
- private void startMediaPickerDataLoader() {
- mBindingRef.getData().startLoader(MediaPickerData.GALLERY_IMAGE_LOADER, mBindingRef, null,
- this);
- }
-
- @Override
- protected void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (requestCode == MediaPicker.GALLERY_PERMISSION_REQUEST_CODE) {
- final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
- if (permissionGranted) {
- startMediaPickerDataLoader();
- }
- updateForPermissionState(permissionGranted);
- }
- }
-
- private void updateForPermissionState(final boolean granted) {
- // onRequestPermissionsResult can sometimes get called before createView().
- if (mGalleryGridView == null) {
- return;
- }
-
- mGalleryGridView.setVisibility(granted ? View.VISIBLE : View.GONE);
- mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java
deleted file mode 100644
index 45d9579..0000000
--- a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.view.TextureView;
-import android.view.View;
-
-import java.io.IOException;
-
-/**
- * A hardware accelerated preview texture for the camera. This is the preferred CameraPreview
- * because it animates smoother. When hardware acceleration isn't available, SoftwareCameraPreview
- * is used.
- *
- * There is a significant amount of duplication between HardwareCameraPreview and
- * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The
- * implementations of the shared methods are delegated to CameraPreview
- */
-public class HardwareCameraPreview extends TextureView implements CameraPreview.CameraPreviewHost {
- private CameraPreview mPreview;
-
- public HardwareCameraPreview(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mPreview = new CameraPreview(this);
- setSurfaceTextureListener(new SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(final SurfaceTexture surfaceTexture, final int i, final int i2) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(final SurfaceTexture surfaceTexture, final int i, final int i2) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(final SurfaceTexture surfaceTexture) {
- CameraManager.get().setSurface(null);
- return true;
- }
-
- @Override
- public void onSurfaceTextureUpdated(final SurfaceTexture surfaceTexture) {
- CameraManager.get().setSurface(mPreview);
- }
- });
- }
-
- @Override
- protected void onVisibilityChanged(final View changedView, final int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- mPreview.onVisibilityChanged(visibility);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mPreview.onDetachedFromWindow();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mPreview.onAttachedToWindow();
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- super.onRestoreInstanceState(state);
- mPreview.onRestoreInstanceState();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public View getView() {
- return this;
- }
-
- @Override
- public boolean isValid() {
- return getSurfaceTexture() != null;
- }
-
- @Override
- public void startPreview(final Camera camera) throws IOException {
- camera.setPreviewTexture(getSurfaceTexture());
- }
-
- @Override
- public void onCameraPermissionGranted() {
- mPreview.onCameraPermissionGranted();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java b/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java
deleted file mode 100644
index 637eb84..0000000
--- a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.net.Uri;
-
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.exif.ExifInterface;
-import com.android.messaging.util.exif.ExifTag;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class ImagePersistTask extends SafeAsyncTask<Void, Void, Void> {
- private static final String JPEG_EXTENSION = "jpg";
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private int mWidth;
- private int mHeight;
- private final float mHeightPercent;
- private final byte[] mBytes;
- private final Context mContext;
- private final CameraManager.MediaCallback mCallback;
- private Uri mOutputUri;
- private Exception mException;
-
- public ImagePersistTask(
- final int width,
- final int height,
- final float heightPercent,
- final byte[] bytes,
- final Context context,
- final CameraManager.MediaCallback callback) {
- Assert.isTrue(heightPercent >= 0 && heightPercent <= 1);
- Assert.notNull(bytes);
- Assert.notNull(context);
- Assert.notNull(callback);
- mWidth = width;
- mHeight = height;
- mHeightPercent = heightPercent;
- mBytes = bytes;
- mContext = context;
- mCallback = callback;
- // TODO: We probably want to store directly in MMS storage to prevent this
- // intermediate step
- mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(JPEG_EXTENSION);
- }
-
- @Override
- protected Void doInBackgroundTimed(final Void... params) {
- OutputStream outputStream = null;
- Bitmap bitmap = null;
- Bitmap clippedBitmap = null;
- try {
- outputStream =
- mContext.getContentResolver().openOutputStream(mOutputUri);
- if (mHeightPercent != 1.0f) {
- int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED;
- final ExifInterface exifInterface = new ExifInterface();
- try {
- exifInterface.readExif(mBytes);
- final Integer orientationValue =
- exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION);
- if (orientationValue != null) {
- orientation = orientationValue.intValue();
- }
- // The thumbnail is of the full image, but we're cropping it, so just clear
- // the thumbnail
- exifInterface.setCompressedThumbnail((byte[]) null);
- } catch (IOException e) {
- // Couldn't get exif tags, not the end of the world
- }
- bitmap = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length);
- final int clippedWidth;
- final int clippedHeight;
- if (ExifInterface.getOrientationParams(orientation).invertDimensions) {
- Assert.equals(mWidth, bitmap.getHeight());
- Assert.equals(mHeight, bitmap.getWidth());
- clippedWidth = (int) (mHeight * mHeightPercent);
- clippedHeight = mWidth;
- } else {
- Assert.equals(mWidth, bitmap.getWidth());
- Assert.equals(mHeight, bitmap.getHeight());
- clippedWidth = mWidth;
- clippedHeight = (int) (mHeight * mHeightPercent);
- }
- final int offsetTop = (bitmap.getHeight() - clippedHeight) / 2;
- final int offsetLeft = (bitmap.getWidth() - clippedWidth) / 2;
- mWidth = clippedWidth;
- mHeight = clippedHeight;
- clippedBitmap = Bitmap.createBitmap(clippedWidth, clippedHeight,
- Bitmap.Config.ARGB_8888);
- clippedBitmap.setDensity(bitmap.getDensity());
- final Canvas clippedBitmapCanvas = new Canvas(clippedBitmap);
- final Matrix matrix = new Matrix();
- matrix.postTranslate(-offsetLeft, -offsetTop);
- clippedBitmapCanvas.drawBitmap(bitmap, matrix, null /* paint */);
- clippedBitmapCanvas.save();
- // EXIF data can take a big chunk of the file size and is often cleared by the
- // carrier, only store orientation since that's critical
- ExifTag orientationTag = exifInterface.getTag(ExifInterface.TAG_ORIENTATION);
- exifInterface.clearExif();
- exifInterface.setTag(orientationTag);
- exifInterface.writeExif(clippedBitmap, outputStream);
- } else {
- outputStream.write(mBytes);
- }
- } catch (final IOException e) {
- mOutputUri = null;
- mException = e;
- LogUtil.e(TAG, "Unable to persist image to temp storage " + e);
- } finally {
- if (bitmap != null) {
- bitmap.recycle();
- }
-
- if (clippedBitmap != null) {
- clippedBitmap.recycle();
- }
-
- if (outputStream != null) {
- try {
- outputStream.flush();
- } catch (final IOException e) {
- mOutputUri = null;
- mException = e;
- LogUtil.e(TAG, "error trying to flush and close the outputStream" + e);
- } finally {
- try {
- outputStream.close();
- } catch (final IOException e) {
- // Do nothing.
- }
- }
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(final Void aVoid) {
- if (mOutputUri != null) {
- mCallback.onMediaReady(mOutputUri, ContentType.IMAGE_JPEG, mWidth, mHeight);
- } else {
- Assert.notNull(mException);
- mCallback.onMediaFailed(mException);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java b/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java
deleted file mode 100644
index 06730a3..0000000
--- a/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.mediapicker;
-
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UiUtils;
-
-import java.io.IOException;
-
-/**
- * Wraps around the functionalities of MediaRecorder, performs routine setup for audio recording
- * and updates the audio level to be displayed in UI.
- *
- * During the start and end of a recording session, we kick off a thread that polls for audio
- * levels, and updates the thread-safe AudioLevelSource instance. Consumers may bind to the
- * sound level by either polling from the level source, or register for a level change callback
- * on the level source object. In Bugle, the UI element (SoundLevels) polls for the sound level
- * on the UI thread by using animation ticks and invalidating itself.
- *
- * Aside from tracking sound levels, this also encapsulates the functionality to save the file
- * to the scratch space. The saved file is returned by calling stopRecording().
- */
-public class LevelTrackingMediaRecorder {
- // We refresh sound level every 100ms during a recording session.
- private static final int REFRESH_INTERVAL_MILLIS = 100;
-
- // The native amplitude returned from MediaRecorder ranges from 0~32768 (unfortunately, this
- // is not a constant that's defined anywhere, but the framework's Recorder app is using the
- // same hard-coded number). Therefore, a constant is needed in order to make it 0~100.
- private static final int MAX_AMPLITUDE_FACTOR = 32768 / 100;
-
- // We want to limit the max audio file size by the max message size allowed by MmsConfig,
- // plus multiplied by this fudge ratio to guarantee that we don't go over limit.
- private static final float MAX_SIZE_RATIO = 0.8f;
-
- // Default recorder settings for Bugle.
- // TODO: Do we want these to be tweakable?
- private static final int MEDIA_RECORDER_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
- private static final int MEDIA_RECORDER_OUTPUT_FORMAT = MediaRecorder.OutputFormat.THREE_GPP;
- private static final int MEDIA_RECORDER_AUDIO_ENCODER = MediaRecorder.AudioEncoder.AMR_NB;
-
- private final AudioLevelSource mLevelSource;
- private Thread mRefreshLevelThread;
- private MediaRecorder mRecorder;
- private Uri mOutputUri;
- private ParcelFileDescriptor mOutputFD;
-
- public LevelTrackingMediaRecorder() {
- mLevelSource = new AudioLevelSource();
- }
-
- public AudioLevelSource getLevelSource() {
- return mLevelSource;
- }
-
- /**
- * @return if we are currently in a recording session.
- */
- public boolean isRecording() {
- return mRecorder != null;
- }
-
- /**
- * Start a new recording session.
- * @return true if a session is successfully started; false if something went wrong or if
- * we are already recording.
- */
- public boolean startRecording(final MediaRecorder.OnErrorListener errorListener,
- final MediaRecorder.OnInfoListener infoListener, int maxSize) {
- synchronized (LevelTrackingMediaRecorder.class) {
- if (mRecorder == null) {
- mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(
- ContentType.THREE_GPP_EXTENSION);
- mRecorder = new MediaRecorder();
- try {
- // The scratch space file is a Uri, however MediaRecorder
- // API only accepts absolute FD's. Therefore, get the
- // FileDescriptor from the content resolver to ensure the
- // directory is created and get the file path to output the
- // audio to.
- maxSize *= MAX_SIZE_RATIO;
- mOutputFD = Factory.get().getApplicationContext()
- .getContentResolver().openFileDescriptor(mOutputUri, "w");
- mRecorder.setAudioSource(MEDIA_RECORDER_AUDIO_SOURCE);
- mRecorder.setOutputFormat(MEDIA_RECORDER_OUTPUT_FORMAT);
- mRecorder.setAudioEncoder(MEDIA_RECORDER_AUDIO_ENCODER);
- mRecorder.setOutputFile(mOutputFD.getFileDescriptor());
- mRecorder.setMaxFileSize(maxSize);
- mRecorder.setOnErrorListener(errorListener);
- mRecorder.setOnInfoListener(infoListener);
- mRecorder.prepare();
- mRecorder.start();
- startTrackingSoundLevel();
- return true;
- } catch (final Exception e) {
- // There may be a device failure or I/O failure, record the error but
- // don't fail.
- LogUtil.e(LogUtil.BUGLE_TAG, "Something went wrong when starting " +
- "media recorder. " + e);
- UiUtils.showToastAtBottom(R.string.audio_recording_start_failed);
- stopRecording();
- }
- } else {
- Assert.fail("Trying to start a new recording session while already recording!");
- }
- return false;
- }
- }
-
- /**
- * Stop the current recording session.
- * @return the Uri of the output file, or null if not currently recording.
- */
- public Uri stopRecording() {
- synchronized (LevelTrackingMediaRecorder.class) {
- if (mRecorder != null) {
- try {
- mRecorder.stop();
- } catch (final RuntimeException ex) {
- // This may happen when the recording is too short, so just drop the recording
- // in this case.
- LogUtil.w(LogUtil.BUGLE_TAG, "Something went wrong when stopping " +
- "media recorder. " + ex);
- if (mOutputUri != null) {
- final Uri outputUri = mOutputUri;
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- Factory.get().getApplicationContext().getContentResolver().delete(
- outputUri, null, null);
- }
- });
- mOutputUri = null;
- }
- } finally {
- mRecorder.release();
- mRecorder = null;
- }
- } else {
- Assert.fail("Not currently recording!");
- return null;
- }
- }
-
- if (mOutputFD != null) {
- try {
- mOutputFD.close();
- } catch (final IOException e) {
- // Nothing to do
- }
- mOutputFD = null;
- }
-
- stopTrackingSoundLevel();
- return mOutputUri;
- }
-
- private int getAmplitude() {
- synchronized (LevelTrackingMediaRecorder.class) {
- if (mRecorder != null) {
- final int maxAmplitude = mRecorder.getMaxAmplitude() / MAX_AMPLITUDE_FACTOR;
- return Math.min(maxAmplitude, 100);
- } else {
- return 0;
- }
- }
- }
-
- private void startTrackingSoundLevel() {
- stopTrackingSoundLevel();
- mRefreshLevelThread = new Thread() {
- @Override
- public void run() {
- try {
- while (true) {
- synchronized (LevelTrackingMediaRecorder.class) {
- if (mRecorder != null) {
- mLevelSource.setSpeechLevel(getAmplitude());
- } else {
- // The recording session is over, finish the thread.
- return;
- }
- }
- Thread.sleep(REFRESH_INTERVAL_MILLIS);
- }
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- };
- mRefreshLevelThread.start();
- }
-
- private void stopTrackingSoundLevel() {
- if (mRefreshLevelThread != null && mRefreshLevelThread.isAlive()) {
- mRefreshLevelThread.interrupt();
- mRefreshLevelThread = null;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaChooser.java b/src/com/android/messaging/ui/mediapicker/MediaChooser.java
deleted file mode 100644
index 9ac0d1b..0000000
--- a/src/com/android/messaging/ui/mediapicker/MediaChooser.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.app.FragmentManager;
-import android.content.Context;
-import android.support.v7.app.ActionBar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.MediaPickerData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.ui.BasePagerViewHolder;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.OsUtil;
-
-abstract class MediaChooser extends BasePagerViewHolder
- implements DraftMessageSubscriptionDataProvider {
- /** The media picker that the chooser is hosted in */
- protected final MediaPicker mMediaPicker;
-
- /** Referencing the main media picker binding to perform data loading */
- protected final ImmutableBindingRef<MediaPickerData> mBindingRef;
-
- /** True if this is the selected chooser */
- protected boolean mSelected;
-
- /** True if this chooser is open */
- protected boolean mOpen;
-
- /** The button to show in the tab strip */
- private ImageButton mTabButton;
-
- /** Used by subclasses to indicate that no loader is required from the data model in order for
- * this chooser to function.
- */
- public static final int NO_LOADER_REQUIRED = -1;
-
- /**
- * Initializes a new instance of the Chooser class
- * @param mediaPicker The media picker that the chooser is hosted in
- */
- MediaChooser(final MediaPicker mediaPicker) {
- Assert.notNull(mediaPicker);
- mMediaPicker = mediaPicker;
- mBindingRef = mediaPicker.getMediaPickerDataBinding();
- mSelected = false;
- }
-
- protected void setSelected(final boolean selected) {
- mSelected = selected;
- if (selected) {
- // If we're selected, it must be open
- mOpen = true;
- }
- if (mTabButton != null) {
- mTabButton.setSelected(selected);
- mTabButton.setAlpha(selected ? 1 : 0.5f);
- }
- }
-
- ImageButton getTabButton() {
- return mTabButton;
- }
-
- void onCreateTabButton(final LayoutInflater inflater, final ViewGroup parent) {
- mTabButton = (ImageButton) inflater.inflate(
- R.layout.mediapicker_tab_button,
- parent,
- false /* addToParent */);
- mTabButton.setImageResource(getIconResource());
- mTabButton.setContentDescription(
- inflater.getContext().getResources().getString(getIconDescriptionResource()));
- setSelected(mSelected);
- mTabButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- mMediaPicker.selectChooser(MediaChooser.this);
- }
- });
- }
-
- protected Context getContext() {
- return mMediaPicker.getActivity();
- }
-
- protected FragmentManager getFragmentManager() {
- return OsUtil.isAtLeastJB_MR1() ? mMediaPicker.getChildFragmentManager() :
- mMediaPicker.getFragmentManager();
- }
- protected LayoutInflater getLayoutInflater() {
- return LayoutInflater.from(getContext());
- }
-
- /** Allows the chooser to handle full screen change */
- void onFullScreenChanged(final boolean fullScreen) {}
-
- /** Allows the chooser to handle the chooser being opened or closed */
- void onOpenedChanged(final boolean open) {
- mOpen = open;
- }
-
- /** @return The bit field of media types that this chooser can pick */
- public abstract int getSupportedMediaTypes();
-
- /** @return The resource id of the icon for the chooser */
- abstract int getIconResource();
-
- /** @return The resource id of the string to use for the accessibility text of the icon */
- abstract int getIconDescriptionResource();
-
- /**
- * Sets up the action bar to show the current state of the full-screen chooser
- * @param actionBar The action bar to populate
- */
- void updateActionBar(final ActionBar actionBar) {
- final int actionBarTitleResId = getActionBarTitleResId();
- if (actionBarTitleResId == 0) {
- actionBar.hide();
- } else {
- actionBar.setCustomView(null);
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.show();
- // Use X instead of <- in the action bar
- actionBar.setHomeAsUpIndicator(R.drawable.ic_remove_small_light);
- actionBar.setTitle(actionBarTitleResId);
- }
- }
-
- /**
- * Returns the resource Id used for the action bar title.
- */
- abstract int getActionBarTitleResId();
-
- /**
- * Throws an exception if the media chooser object doesn't require data support.
- */
- public void onDataUpdated(final Object data, final int loaderId) {
- throw new IllegalStateException();
- }
-
- /**
- * Called by the MediaPicker to determine whether this panel can be swiped down further. If
- * not, then a swipe down gestured will be captured by the MediaPickerPanel to shrink the
- * entire panel.
- */
- public boolean canSwipeDown() {
- return false;
- }
-
- /**
- * Typically the media picker is closed when the IME is opened, but this allows the chooser to
- * specify that showing the IME is okay while the chooser is up
- */
- public boolean canShowIme() {
- return false;
- }
-
- public boolean onBackPressed() {
- return false;
- }
-
- public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
- }
-
- public boolean onOptionsItemSelected(final MenuItem item) {
- return false;
- }
-
- public void setThemeColor(final int color) {
- }
-
- /**
- * Returns true if the chooser is owning any incoming touch events, so that the media picker
- * panel won't process it and slide the panel.
- */
- public boolean isHandlingTouch() {
- return false;
- }
-
- public void stopTouchHandling() {
- }
-
- @Override
- public int getConversationSelfSubId() {
- return mMediaPicker.getConversationSelfSubId();
- }
-
- /** Optional activity life-cycle methods to be overridden by subclasses */
- public void onPause() { }
- public void onResume() { }
- protected void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) { }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
deleted file mode 100644
index f441d09..0000000
--- a/src/com/android/messaging/ui/mediapicker/MediaPicker.java
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.support.v7.app.ActionBar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.MediaPickerData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.FixedViewPagerAdapter;
-import com.android.messaging.ui.mediapicker.DocumentImagePicker.SelectionListener;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Fragment used to select or capture media to be added to the message
- */
-public class MediaPicker extends Fragment implements DraftMessageSubscriptionDataProvider {
- /** The listener interface for events from the media picker */
- public interface MediaPickerListener {
- /** Called when the media picker is opened so the host can accommodate the UI */
- void onOpened();
-
- /**
- * Called when the media picker goes into or leaves full screen mode so the host can
- * accommodate the fullscreen UI
- */
- void onFullScreenChanged(boolean fullScreen);
-
- /**
- * Called when the user selects one or more items
- * @param items The list of items which were selected
- */
- void onItemsSelected(Collection<MessagePartData> items, boolean dismissMediaPicker);
-
- /**
- * Called when the user unselects one item.
- */
- void onItemUnselected(MessagePartData item);
-
- /**
- * Called when the media picker is closed. Always called immediately after onItemsSelected
- */
- void onDismissed();
-
- /**
- * Called when media item selection is confirmed in a multi-select action.
- */
- void onConfirmItemSelection();
-
- /**
- * Called when a pending attachment is added.
- * @param pendingItem the pending attachment data being loaded.
- */
- void onPendingItemAdded(PendingAttachmentData pendingItem);
-
- /**
- * Called when a new media chooser is selected.
- */
- void onChooserSelected(final int chooserIndex);
- }
-
- /** The tag used when registering and finding this fragment */
- public static final String FRAGMENT_TAG = "mediapicker";
-
- // Media type constants that the media picker supports
- public static final int MEDIA_TYPE_DEFAULT = 0x0000;
- public static final int MEDIA_TYPE_NONE = 0x0000;
- public static final int MEDIA_TYPE_IMAGE = 0x0001;
- public static final int MEDIA_TYPE_VIDEO = 0x0002;
- public static final int MEDIA_TYPE_AUDIO = 0x0004;
- public static final int MEDIA_TYPE_VCARD = 0x0008;
- public static final int MEDIA_TYPE_LOCATION = 0x0010;
- private static final int MEDA_TYPE_INVALID = 0x0020;
- public static final int MEDIA_TYPE_ALL = 0xFFFF;
-
- /** The listener to call when events occur */
- private MediaPickerListener mListener;
-
- /** The handler used to dispatch calls to the listener */
- private Handler mListenerHandler;
-
- /** The bit flags of media types supported */
- private int mSupportedMediaTypes;
-
- /** The list of choosers which could be within the media picker */
- private final MediaChooser[] mChoosers;
-
- /** The list of currently enabled choosers */
- private final ArrayList<MediaChooser> mEnabledChoosers;
-
- /** The currently selected chooser */
- private MediaChooser mSelectedChooser;
-
- /** The main panel that controls the custom layout */
- private MediaPickerPanel mMediaPickerPanel;
-
- /** The linear layout that holds the icons to select individual chooser tabs */
- private LinearLayout mTabStrip;
-
- /** The view pager to swap between choosers */
- private ViewPager mViewPager;
-
- /** The current pager adapter for the view pager */
- private FixedViewPagerAdapter<MediaChooser> mPagerAdapter;
-
- /** True if the media picker is visible */
- private boolean mOpen;
-
- /** The theme color to use to make the media picker match the rest of the UI */
- private int mThemeColor;
-
- @VisibleForTesting
- final Binding<MediaPickerData> mBinding = BindingBase.createBinding(this);
-
- /** Handles picking image from the document picker */
- private DocumentImagePicker mDocumentImagePicker;
-
- /** Provides subscription-related data to access per-subscription configurations. */
- private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
-
- /** Provides access to DraftMessageData associated with the current conversation */
- private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
-
- public MediaPicker() {
- this(Factory.get().getApplicationContext());
- }
-
- public MediaPicker(final Context context) {
- mBinding.bind(DataModel.get().createMediaPickerData(context));
- mEnabledChoosers = new ArrayList<MediaChooser>();
- mChoosers = new MediaChooser[] {
- new CameraMediaChooser(this),
- new GalleryMediaChooser(this),
- new AudioMediaChooser(this),
- };
-
- mOpen = false;
- setSupportedMediaTypes(MEDIA_TYPE_ALL);
- }
-
- private boolean mIsAttached;
- private int mStartingMediaTypeOnAttach = MEDA_TYPE_INVALID;
- private boolean mAnimateOnAttach;
-
- @Override
- public void onAttach (final Activity activity) {
- super.onAttach(activity);
- mIsAttached = true;
- if (mStartingMediaTypeOnAttach != MEDA_TYPE_INVALID) {
- // open() was previously called. Do the pending open now.
- doOpen(mStartingMediaTypeOnAttach, mAnimateOnAttach);
- }
- }
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mBinding.getData().init(getLoaderManager());
- mDocumentImagePicker = new DocumentImagePicker(this,
- new SelectionListener() {
- @Override
- public void onDocumentSelected(final PendingAttachmentData data) {
- if (mBinding.isBound()) {
- dispatchPendingItemAdded(data);
- }
- }
- });
- }
-
- @Override
- public View onCreateView(
- final LayoutInflater inflater,
- final ViewGroup container,
- final Bundle savedInstanceState) {
- mMediaPickerPanel = (MediaPickerPanel) inflater.inflate(
- R.layout.mediapicker_fragment,
- container,
- false);
- mMediaPickerPanel.setMediaPicker(this);
- mTabStrip = (LinearLayout) mMediaPickerPanel.findViewById(R.id.mediapicker_tabstrip);
- mTabStrip.setBackgroundColor(mThemeColor);
- for (final MediaChooser chooser : mChoosers) {
- chooser.onCreateTabButton(inflater, mTabStrip);
- final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
- MEDIA_TYPE_NONE;
- final ImageButton tabButton = chooser.getTabButton();
- if (tabButton != null) {
- tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
- mTabStrip.addView(tabButton);
- }
- }
-
- mViewPager = (ViewPager) mMediaPickerPanel.findViewById(R.id.mediapicker_view_pager);
- mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
- @Override
- public void onPageScrolled(
- final int position,
- final float positionOffset,
- final int positionOffsetPixels) {
- }
-
- @Override
- public void onPageSelected(int position) {
- // The position returned is relative to if we are in RtL mode. This class never
- // switches the indices of the elements if we are in RtL mode so we need to
- // translate the index back. For example, if the user clicked the item most to the
- // right in RtL mode we would want the index to appear as 0 here, however the
- // position returned would the last possible index.
- if (UiUtils.isRtlMode()) {
- position = mEnabledChoosers.size() - 1 - position;
- }
- selectChooser(mEnabledChoosers.get(position));
- }
-
- @Override
- public void onPageScrollStateChanged(final int state) {
- }
- });
- // Camera initialization is expensive, so don't realize offscreen pages if not needed.
- mViewPager.setOffscreenPageLimit(0);
- mViewPager.setAdapter(mPagerAdapter);
- final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
- getActivity());
- mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
- mMediaPickerPanel.setExpanded(mOpen, true, mEnabledChoosers.indexOf(mSelectedChooser));
- return mMediaPickerPanel;
- }
-
- @Override
- public void onPause() {
- super.onPause();
- CameraManager.get().onPause();
- for (final MediaChooser chooser : mEnabledChoosers) {
- chooser.onPause();
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- CameraManager.get().onResume();
-
- for (final MediaChooser chooser : mEnabledChoosers) {
- chooser.onResume();
- }
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- mDocumentImagePicker.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- /**
- * Sets the theme color to make the media picker match the surrounding UI
- * @param themeColor The new theme color
- */
- public void setConversationThemeColor(final int themeColor) {
- mThemeColor = themeColor;
- if (mTabStrip != null) {
- mTabStrip.setBackgroundColor(mThemeColor);
- }
-
- for (final MediaChooser chooser : mEnabledChoosers) {
- chooser.setThemeColor(mThemeColor);
- }
- }
-
- /**
- * Gets the current conversation theme color.
- */
- public int getConversationThemeColor() {
- return mThemeColor;
- }
-
- public void setDraftMessageDataModel(final BindingBase<DraftMessageData> draftBinding) {
- mDraftMessageDataModel = Binding.createBindingReference(draftBinding);
- }
-
- public ImmutableBindingRef<DraftMessageData> getDraftMessageDataModel() {
- return mDraftMessageDataModel;
- }
-
- public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
- mSubscriptionDataProvider = provider;
- }
-
- @Override
- public int getConversationSelfSubId() {
- return mSubscriptionDataProvider.getConversationSelfSubId();
- }
-
- /**
- * Opens the media picker and optionally shows the chooser for the supplied media type
- * @param startingMediaType The media type of the chooser to open if {@link #MEDIA_TYPE_DEFAULT}
- * is used, then the default chooser from saved shared prefs is opened
- */
- public void open(final int startingMediaType, final boolean animate) {
- mOpen = true;
- if (mIsAttached) {
- doOpen(startingMediaType, animate);
- } else {
- // open() can get called immediately after the MediaPicker is created. In that case,
- // we defer doing work as it may require an attached fragment (eg. calling
- // Fragment#requestPermission)
- mStartingMediaTypeOnAttach = startingMediaType;
- mAnimateOnAttach = animate;
- }
- }
-
- private void doOpen(int startingMediaType, final boolean animate) {
- final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
- // getActivity() will be null at this point
- Factory.get().getApplicationContext());
-
- // If no specific starting type is specified (i.e. MEDIA_TYPE_DEFAULT), try to get the
- // last opened chooser index from shared prefs.
- if (startingMediaType == MEDIA_TYPE_DEFAULT) {
- final int selectedChooserIndex = mBinding.getData().getSelectedChooserIndex();
- if (selectedChooserIndex >= 0 && selectedChooserIndex < mEnabledChoosers.size()) {
- selectChooser(mEnabledChoosers.get(selectedChooserIndex));
- } else {
- // This is the first time the picker is being used
- if (isTouchExplorationEnabled) {
- // Accessibility defaults to audio attachment mode.
- startingMediaType = MEDIA_TYPE_AUDIO;
- }
- }
- }
-
- if (mSelectedChooser == null) {
- for (final MediaChooser chooser : mEnabledChoosers) {
- if (startingMediaType == MEDIA_TYPE_DEFAULT ||
- (startingMediaType & chooser.getSupportedMediaTypes()) != MEDIA_TYPE_NONE) {
- selectChooser(chooser);
- break;
- }
- }
- }
-
- if (mSelectedChooser == null) {
- // Fall back to the first chooser.
- selectChooser(mEnabledChoosers.get(0));
- }
-
- if (mMediaPickerPanel != null) {
- mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
- mMediaPickerPanel.setExpanded(true, animate,
- mEnabledChoosers.indexOf(mSelectedChooser));
- }
- }
-
- /** @return True if the media picker is open */
- public boolean isOpen() {
- return mOpen;
- }
-
- /**
- * Sets the list of media types to allow the user to select
- * @param mediaTypes The bit flags of media types to allow. Can be any combination of the
- * MEDIA_TYPE_* values
- */
- void setSupportedMediaTypes(final int mediaTypes) {
- mSupportedMediaTypes = mediaTypes;
- mEnabledChoosers.clear();
- boolean selectNextChooser = false;
- for (final MediaChooser chooser : mChoosers) {
- final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
- MEDIA_TYPE_NONE;
- if (enabled) {
- // TODO Add a way to inform the chooser which media types are supported
- mEnabledChoosers.add(chooser);
- if (selectNextChooser) {
- selectChooser(chooser);
- selectNextChooser = false;
- }
- } else if (mSelectedChooser == chooser) {
- selectNextChooser = true;
- }
- final ImageButton tabButton = chooser.getTabButton();
- if (tabButton != null) {
- tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
- }
- }
-
- if (selectNextChooser && mEnabledChoosers.size() > 0) {
- selectChooser(mEnabledChoosers.get(0));
- }
- final MediaChooser[] enabledChoosers = new MediaChooser[mEnabledChoosers.size()];
- mEnabledChoosers.toArray(enabledChoosers);
- mPagerAdapter = new FixedViewPagerAdapter<MediaChooser>(enabledChoosers);
- if (mViewPager != null) {
- mViewPager.setAdapter(mPagerAdapter);
- }
-
- // Only rebind data if we are currently bound. Otherwise, we must have not
- // bound to any data yet and should wait until onCreate() to bind data.
- if (mBinding.isBound() && getActivity() != null) {
- mBinding.unbind();
- mBinding.bind(DataModel.get().createMediaPickerData(getActivity()));
- mBinding.getData().init(getLoaderManager());
- }
- }
-
- ViewPager getViewPager() {
- return mViewPager;
- }
-
- /** Hides the media picker, and frees up any resources it’s using */
- public void dismiss(final boolean animate) {
- mOpen = false;
- if (mMediaPickerPanel != null) {
- mMediaPickerPanel.setExpanded(false, animate, MediaPickerPanel.PAGE_NOT_SET);
- }
- mSelectedChooser = null;
- }
-
- /**
- * Sets the listener for the media picker events
- * @param listener The listener which will receive events
- */
- public void setListener(final MediaPickerListener listener) {
- Assert.isMainThread();
- mListener = listener;
- mListenerHandler = listener != null ? new Handler() : null;
- }
-
- /** @return True if the media picker is in full-screen mode */
- public boolean isFullScreen() {
- return mMediaPickerPanel != null && mMediaPickerPanel.isFullScreen();
- }
-
- public void setFullScreen(final boolean fullScreen) {
- mMediaPickerPanel.setFullScreenView(fullScreen, true);
- }
-
- public void updateActionBar(final ActionBar actionBar) {
- if (getActivity() == null) {
- return;
- }
- if (isFullScreen() && mSelectedChooser != null) {
- mSelectedChooser.updateActionBar(actionBar);
- } else {
- actionBar.hide();
- }
- }
-
- /**
- * Selects a new chooser
- * @param newSelectedChooser The newly selected chooser
- */
- void selectChooser(final MediaChooser newSelectedChooser) {
- if (mSelectedChooser == newSelectedChooser) {
- return;
- }
-
- if (mSelectedChooser != null) {
- mSelectedChooser.setSelected(false);
- }
- mSelectedChooser = newSelectedChooser;
- if (mSelectedChooser != null) {
- mSelectedChooser.setSelected(true);
- }
-
- final int chooserIndex = mEnabledChoosers.indexOf(mSelectedChooser);
- if (mViewPager != null) {
- mViewPager.setCurrentItem(chooserIndex, true /* smoothScroll */);
- }
-
- if (isFullScreen()) {
- invalidateOptionsMenu();
- }
-
- // Save the newly selected chooser's index so we may directly switch to it the
- // next time user opens the media picker.
- mBinding.getData().saveSelectedChooserIndex(chooserIndex);
- if (mMediaPickerPanel != null) {
- mMediaPickerPanel.onChooserChanged();
- }
- dispatchChooserSelected(chooserIndex);
- }
-
- public boolean canShowIme() {
- if (mSelectedChooser != null) {
- return mSelectedChooser.canShowIme();
- }
- return false;
- }
-
- public boolean onBackPressed() {
- return mSelectedChooser != null && mSelectedChooser.onBackPressed();
- }
-
- void invalidateOptionsMenu() {
- ((BugleActionBarActivity) getActivity()).supportInvalidateOptionsMenu();
- }
-
- void dispatchOpened() {
- setHasOptionsMenu(false);
- mOpen = true;
- mPagerAdapter.notifyDataSetChanged();
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onOpened();
- }
- });
- }
- if (mSelectedChooser != null) {
- mSelectedChooser.onFullScreenChanged(false);
- mSelectedChooser.onOpenedChanged(true);
- }
- }
-
- void dispatchDismissed() {
- setHasOptionsMenu(false);
- mOpen = false;
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onDismissed();
- }
- });
- }
- if (mSelectedChooser != null) {
- mSelectedChooser.onOpenedChanged(false);
- }
- }
-
- void dispatchFullScreen(final boolean fullScreen) {
- setHasOptionsMenu(fullScreen);
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onFullScreenChanged(fullScreen);
- }
- });
- }
- if (mSelectedChooser != null) {
- mSelectedChooser.onFullScreenChanged(fullScreen);
- }
- }
-
- void dispatchItemsSelected(final MessagePartData item, final boolean dismissMediaPicker) {
- final List<MessagePartData> items = new ArrayList<MessagePartData>(1);
- items.add(item);
- dispatchItemsSelected(items, dismissMediaPicker);
- }
-
- void dispatchItemsSelected(final Collection<MessagePartData> items,
- final boolean dismissMediaPicker) {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onItemsSelected(items, dismissMediaPicker);
- }
- });
- }
-
- if (isFullScreen() && !dismissMediaPicker) {
- invalidateOptionsMenu();
- }
- }
-
- void dispatchItemUnselected(final MessagePartData item) {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onItemUnselected(item);
- }
- });
- }
-
- if (isFullScreen()) {
- invalidateOptionsMenu();
- }
- }
-
- void dispatchConfirmItemSelection() {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onConfirmItemSelection();
- }
- });
- }
- }
-
- void dispatchPendingItemAdded(final PendingAttachmentData pendingItem) {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onPendingItemAdded(pendingItem);
- }
- });
- }
-
- if (isFullScreen()) {
- invalidateOptionsMenu();
- }
- }
-
- void dispatchChooserSelected(final int chooserIndex) {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onChooserSelected(chooserIndex);
- }
- });
- }
- }
-
- public boolean canSwipeDownChooser() {
- return mSelectedChooser == null ? false : mSelectedChooser.canSwipeDown();
- }
-
- public boolean isChooserHandlingTouch() {
- return mSelectedChooser == null ? false : mSelectedChooser.isHandlingTouch();
- }
-
- public void stopChooserTouchHandling() {
- if (mSelectedChooser != null) {
- mSelectedChooser.stopTouchHandling();
- }
- }
-
- boolean getChooserShowsActionBarInFullScreen() {
- return mSelectedChooser == null ? false : mSelectedChooser.getActionBarTitleResId() != 0;
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- if (mSelectedChooser != null) {
- mSelectedChooser.onCreateOptionsMenu(inflater, menu);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- return (mSelectedChooser != null && mSelectedChooser.onOptionsItemSelected(item)) ||
- super.onOptionsItemSelected(item);
- }
-
- PagerAdapter getPagerAdapter() {
- return mPagerAdapter;
- }
-
- public void resetViewHolderState() {
- mPagerAdapter.resetState();
- }
-
- /**
- * Launch an external picker to pick item from document picker as attachment.
- */
- public void launchDocumentPicker() {
- mDocumentImagePicker.launchPicker();
- }
-
- public ImmutableBindingRef<MediaPickerData> getMediaPickerDataBinding() {
- return BindingBase.createBindingReference(mBinding);
- }
-
- protected static final int CAMERA_PERMISSION_REQUEST_CODE = 1;
- protected static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
- protected static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 3;
- protected static final int GALLERY_PERMISSION_REQUEST_CODE = 4;
-
- @Override
- public void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (mSelectedChooser != null) {
- mSelectedChooser.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java b/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java
deleted file mode 100644
index cc3a4a1..0000000
--- a/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.GridView;
-
-public class MediaPickerGridView extends GridView {
-
- public MediaPickerGridView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- /**
- * Returns if the grid view can be swiped down further. It cannot be swiped down
- * if there's no item or if we are already at the top.
- */
- public boolean canSwipeDown() {
- if (getAdapter() == null || getAdapter().getCount() == 0 || getChildCount() == 0) {
- return false;
- }
-
- final int firstVisiblePosition = getFirstVisiblePosition();
- if (firstVisiblePosition == 0 && getChildAt(0).getTop() >= 0) {
- return false;
- }
- return true;
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java b/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java
deleted file mode 100644
index 56b0a03..0000000
--- a/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.PagingAwareViewPager;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Custom layout panel which makes the MediaPicker animations seamless and synchronized
- * Designed to be very specific to the MediaPicker's usage
- */
-public class MediaPickerPanel extends ViewGroup {
- /**
- * The window of time in which we might to decide to reinterpret the intent of a gesture.
- */
- private static final long TOUCH_RECAPTURE_WINDOW_MS = 500L;
-
- // The two view components to layout
- private LinearLayout mTabStrip;
- private boolean mFullScreenOnly;
- private PagingAwareViewPager mViewPager;
-
- /**
- * True if the MediaPicker is full screen or animating into it
- */
- private boolean mFullScreen;
-
- /**
- * True if the MediaPicker is open at all
- */
- private boolean mExpanded;
-
- /**
- * The current desired height of the MediaPicker. This property may be animated and the
- * measure pass uses it to determine what size the components are.
- */
- private int mCurrentDesiredHeight;
-
- private final Handler mHandler = new Handler();
-
- /**
- * The media picker for dispatching events to the MediaPicker's listener
- */
- private MediaPicker mMediaPicker;
-
- /**
- * The computed default "half-screen" height of the view pager in px
- */
- private final int mDefaultViewPagerHeight;
-
- /**
- * The action bar height used to compute the padding on the view pager when it's full screen.
- */
- private final int mActionBarHeight;
-
- private TouchHandler mTouchHandler;
-
- static final int PAGE_NOT_SET = -1;
-
- public MediaPickerPanel(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- // Cache the computed dimension
- mDefaultViewPagerHeight = getResources().getDimensionPixelSize(
- R.dimen.mediapicker_default_chooser_height);
- mActionBarHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTabStrip = (LinearLayout) findViewById(R.id.mediapicker_tabstrip);
- mViewPager = (PagingAwareViewPager) findViewById(R.id.mediapicker_view_pager);
- mTouchHandler = new TouchHandler();
- setOnTouchListener(mTouchHandler);
- mViewPager.setOnTouchListener(mTouchHandler);
-
- // Make sure full screen mode is updated in landscape mode change when the panel is open.
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- private boolean mLandscapeMode = UiUtils.isLandscapeMode();
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- final boolean newLandscapeMode = UiUtils.isLandscapeMode();
- if (mLandscapeMode != newLandscapeMode) {
- mLandscapeMode = newLandscapeMode;
- if (mExpanded) {
- setExpanded(mExpanded, false /* animate */, mViewPager.getCurrentItem(),
- true /* force */);
- }
- }
- }
- });
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- int requestedHeight = MeasureSpec.getSize(heightMeasureSpec);
- if (mMediaPicker.getChooserShowsActionBarInFullScreen()) {
- requestedHeight -= mActionBarHeight;
- }
- int desiredHeight = Math.min(mCurrentDesiredHeight, requestedHeight);
- if (mExpanded && desiredHeight == 0) {
- // If we want to be shown, we have to have a non-0 height. Returning a height of 0 will
- // cause the framework to abort the animation from 0, so we must always have some
- // height once we start expanding
- desiredHeight = 1;
- } else if (!mExpanded && desiredHeight == 0) {
- mViewPager.setVisibility(View.GONE);
- mViewPager.setAdapter(null);
- }
-
- measureChild(mTabStrip, widthMeasureSpec, heightMeasureSpec);
-
- int tabStripHeight;
- if (requiresFullScreen()) {
- // Ensure that the tab strip is always visible, even in full screen.
- tabStripHeight = mTabStrip.getMeasuredHeight();
- } else {
- // Slide out the tab strip at the end of the animation to full screen.
- tabStripHeight = Math.min(mTabStrip.getMeasuredHeight(),
- requestedHeight - desiredHeight);
- }
-
- // If we are animating and have an interim desired height, use the default height. We can't
- // take the max here as on some devices the mDefaultViewPagerHeight may be too big in
- // landscape mode after animation.
- final int tabAdjustedDesiredHeight = desiredHeight - tabStripHeight;
- final int viewPagerHeight =
- tabAdjustedDesiredHeight <= 1 ? mDefaultViewPagerHeight : tabAdjustedDesiredHeight;
-
- int viewPagerHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
- viewPagerHeight, MeasureSpec.EXACTLY);
- measureChild(mViewPager, widthMeasureSpec, viewPagerHeightMeasureSpec);
- setMeasuredDimension(mViewPager.getMeasuredWidth(), desiredHeight);
- }
-
- @Override
- protected void onLayout(final boolean changed, final int left, final int top, final int right,
- final int bottom) {
- int y = top;
- final int width = right - left;
-
- final int viewPagerHeight = mViewPager.getMeasuredHeight();
- mViewPager.layout(0, y, width, y + viewPagerHeight);
- y += viewPagerHeight;
-
- mTabStrip.layout(0, y, width, y + mTabStrip.getMeasuredHeight());
- }
-
- void onChooserChanged() {
- if (mFullScreen) {
- setDesiredHeight(getDesiredHeight(), true);
- }
- }
-
- void setFullScreenOnly(boolean fullScreenOnly) {
- mFullScreenOnly = fullScreenOnly;
- }
-
- boolean isFullScreen() {
- return mFullScreen;
- }
-
- void setMediaPicker(final MediaPicker mediaPicker) {
- mMediaPicker = mediaPicker;
- }
-
- /**
- * Get the desired height of the media picker panel for when the panel is not in motion (i.e.
- * not being dragged by the user).
- */
- private int getDesiredHeight() {
- if (mFullScreen) {
- int fullHeight = getContext().getResources().getDisplayMetrics().heightPixels;
- if (OsUtil.isAtLeastKLP() && isAttachedToWindow()) {
- // When we're attached to the window, we can get an accurate height, not necessary
- // on older API level devices because they don't include the action bar height
- View composeContainer =
- getRootView().findViewById(R.id.conversation_and_compose_container);
- if (composeContainer != null) {
- // protect against composeContainer having been unloaded already
- fullHeight -= UiUtils.getMeasuredBoundsOnScreen(composeContainer).top;
- }
- }
- if (mMediaPicker.getChooserShowsActionBarInFullScreen()) {
- return fullHeight - mActionBarHeight;
- } else {
- return fullHeight;
- }
- } else if (mExpanded) {
- return LayoutParams.WRAP_CONTENT;
- } else {
- return 0;
- }
- }
-
- private void setupViewPager(final int startingPage) {
- mViewPager.setVisibility(View.VISIBLE);
- if (startingPage >= 0 && startingPage < mMediaPicker.getPagerAdapter().getCount()) {
- mViewPager.setAdapter(mMediaPicker.getPagerAdapter());
- mViewPager.setCurrentItem(startingPage);
- }
- updateViewPager();
- }
-
- /**
- * Expand the media picker panel. Since we always set the pager adapter to null when the panel
- * is collapsed, we need to restore the adapter and the starting page.
- * @param expanded expanded or collapsed
- * @param animate need animation
- * @param startingPage the desired selected page to start
- */
- void setExpanded(final boolean expanded, final boolean animate, final int startingPage) {
- setExpanded(expanded, animate, startingPage, false /* force */);
- }
-
- private void setExpanded(final boolean expanded, final boolean animate, final int startingPage,
- final boolean force) {
- if (expanded == mExpanded && !force) {
- return;
- }
- mFullScreen = false;
- mExpanded = expanded;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- setDesiredHeight(getDesiredHeight(), animate);
- }
- });
- if (expanded) {
- setupViewPager(startingPage);
- mMediaPicker.dispatchOpened();
- } else {
- mMediaPicker.dispatchDismissed();
- }
-
- // Call setFullScreenView() when we are in landscape mode so it can go full screen as
- // soon as it is expanded.
- if (expanded && requiresFullScreen()) {
- setFullScreenView(true, animate);
- }
- }
-
- private boolean requiresFullScreen() {
- return mFullScreenOnly || UiUtils.isLandscapeMode();
- }
-
- private void setDesiredHeight(int height, final boolean animate) {
- final int startHeight = mCurrentDesiredHeight;
- if (height == LayoutParams.WRAP_CONTENT) {
- height = measureHeight();
- }
- clearAnimation();
- if (animate) {
- final int deltaHeight = height - startHeight;
- final Animation animation = new Animation() {
- @Override
- protected void applyTransformation(final float interpolatedTime,
- final Transformation t) {
- mCurrentDesiredHeight = (int) (startHeight + deltaHeight * interpolatedTime);
- requestLayout();
- }
-
- @Override
- public boolean willChangeBounds() {
- return true;
- }
- };
- animation.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
- animation.setInterpolator(UiUtils.EASE_OUT_INTERPOLATOR);
- startAnimation(animation);
- } else {
- mCurrentDesiredHeight = height;
- }
- requestLayout();
- }
-
- /**
- * @return The minimum total height of the view
- */
- private int measureHeight() {
- final int measureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST);
- measureChild(mTabStrip, measureSpec, measureSpec);
- return mDefaultViewPagerHeight + mTabStrip.getMeasuredHeight();
- }
-
- /**
- * Enters or leaves full screen view
- *
- * @param fullScreen True to enter full screen view, false to leave
- * @param animate True to animate the transition
- */
- void setFullScreenView(final boolean fullScreen, final boolean animate) {
- if (fullScreen == mFullScreen) {
- return;
- }
-
- if (requiresFullScreen() && !fullScreen) {
- setExpanded(false /* expanded */, true /* animate */, PAGE_NOT_SET);
- return;
- }
- mFullScreen = fullScreen;
- setDesiredHeight(getDesiredHeight(), animate);
- mMediaPicker.dispatchFullScreen(mFullScreen);
- updateViewPager();
- }
-
- /**
- * ViewPager should have its paging disabled when in full screen mode.
- */
- private void updateViewPager() {
- mViewPager.setPagingEnabled(!mFullScreen);
- }
-
- @Override
- public boolean onInterceptTouchEvent(final MotionEvent ev) {
- return mTouchHandler.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
- }
-
- /**
- * Helper class to handle touch events and swipe gestures
- */
- private class TouchHandler implements OnTouchListener {
- /**
- * The height of the view when the touch press started
- */
- private int mDownHeight = -1;
-
- /**
- * True if the panel moved at all (changed height) during the drag
- */
- private boolean mMoved = false;
-
- // The threshold constants converted from DP to px
- private final float mFlingThresholdPx;
- private final float mBigFlingThresholdPx;
-
- // The system defined pixel size to determine when a movement is considered a drag.
- private final int mTouchSlop;
-
- /**
- * A copy of the MotionEvent that started the drag/swipe gesture
- */
- private MotionEvent mDownEvent;
-
- /**
- * Whether we are currently moving down. We may not be able to move down in full screen
- * mode when the child view can swipe down (such as a list view).
- */
- private boolean mMovedDown = false;
-
- /**
- * Indicates whether the child view contained in the panel can swipe down at the beginning
- * of the drag event (i.e. the initial down). The MediaPanel can contain
- * scrollable children such as a list view / grid view. If the child view can swipe down,
- * We want to let the child view handle the scroll first instead of handling it ourselves.
- */
- private boolean mCanChildViewSwipeDown = false;
-
- /**
- * Necessary direction ratio for a fling to be considered in one direction this prevents
- * horizontal swipes with small vertical components from triggering vertical swipe actions
- */
- private static final float DIRECTION_RATIO = 1.1f;
-
- TouchHandler() {
- final Resources resources = getContext().getResources();
- final ViewConfiguration configuration = ViewConfiguration.get(getContext());
- mFlingThresholdPx = resources.getDimensionPixelSize(
- R.dimen.mediapicker_fling_threshold);
- mBigFlingThresholdPx = resources.getDimensionPixelSize(
- R.dimen.mediapicker_big_fling_threshold);
- mTouchSlop = configuration.getScaledTouchSlop();
- }
-
- /**
- * The media picker panel may contain scrollable children such as a GridView, which eats
- * all touch events before we get to it. Therefore, we'd like to intercept these events
- * before the children to determine if we should handle swiping down in full screen mode.
- * In non-full screen mode, we should handle all vertical scrolling events and leave
- * horizontal scrolling to the view pager.
- */
- public boolean onInterceptTouchEvent(final MotionEvent ev) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- // Never capture the initial down, so that the children may handle it
- // as well. Let the touch handler know about the down event as well.
- mTouchHandler.onTouch(MediaPickerPanel.this, ev);
-
- // Ask the MediaPicker whether the contained view can be swiped down.
- // We record the value at the start of the drag to decide the swiping mode
- // for the entire motion.
- mCanChildViewSwipeDown = mMediaPicker.canSwipeDownChooser();
- return false;
-
- case MotionEvent.ACTION_MOVE: {
- if (mMediaPicker.isChooserHandlingTouch()) {
- if (shouldAllowRecaptureTouch(ev)) {
- mMediaPicker.stopChooserTouchHandling();
- mViewPager.setPagingEnabled(true);
- return false;
- }
- // If the chooser is claiming ownership on all touch events, then we
- // shouldn't try to handle them (neither should the view pager).
- mViewPager.setPagingEnabled(false);
- return false;
- } else if (mCanChildViewSwipeDown) {
- // Never capture event if the child view can swipe down.
- return false;
- } else if (!mFullScreen && mMoved) {
- // When we are not fullscreen, we own any vertical drag motion.
- return true;
- } else if (mMovedDown) {
- // We are currently handling the down swipe ourselves, so always
- // capture this event.
- return true;
- } else {
- // The current interaction mode is undetermined, so always let the
- // touch handler know about this event. However, don't capture this
- // event so the child may handle it as well.
- mTouchHandler.onTouch(MediaPickerPanel.this, ev);
-
- // Capture the touch event from now on if we are handling the drag.
- return mFullScreen ? mMovedDown : mMoved;
- }
- }
- }
- return false;
- }
-
- /**
- * Determine whether we think the user is actually trying to expand or slide despite the
- * fact that they touched first on a chooser that captured the input.
- */
- private boolean shouldAllowRecaptureTouch(MotionEvent ev) {
- final long elapsedMs = ev.getEventTime() - ev.getDownTime();
- if (mDownEvent == null || elapsedMs == 0 || elapsedMs > TOUCH_RECAPTURE_WINDOW_MS) {
- // Either we don't have info to decide or it's been long enough that we no longer
- // want to reinterpret user intent.
- return false;
- }
- final float dx = ev.getRawX() - mDownEvent.getRawX();
- final float dy = ev.getRawY() - mDownEvent.getRawY();
- final float dt = elapsedMs / 1000.0f;
- final float maxAbsDelta = Math.max(Math.abs(dx), Math.abs(dy));
- final float velocity = maxAbsDelta / dt;
- return velocity > mFlingThresholdPx;
- }
-
- @Override
- public boolean onTouch(final View view, final MotionEvent motionEvent) {
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_UP: {
- if (!mMoved || mDownEvent == null) {
- return false;
- }
- final float dx = motionEvent.getRawX() - mDownEvent.getRawX();
- final float dy = motionEvent.getRawY() - mDownEvent.getRawY();
-
- final float dt =
- (motionEvent.getEventTime() - mDownEvent.getEventTime()) / 1000.0f;
- final float yVelocity = dy / dt;
-
- boolean handled = false;
-
- // Vertical swipe occurred if the direction is as least mostly in the y
- // component and has the required velocity (px/sec)
- if ((dx == 0 || (Math.abs(dy) / Math.abs(dx)) > DIRECTION_RATIO) &&
- Math.abs(yVelocity) > mFlingThresholdPx) {
- if (yVelocity < 0 && mExpanded) {
- setFullScreenView(true, true);
- handled = true;
- } else if (yVelocity > 0) {
- if (mFullScreen && yVelocity < mBigFlingThresholdPx) {
- setFullScreenView(false, true);
- } else {
- setExpanded(false, true, PAGE_NOT_SET);
- }
- handled = true;
- }
- }
-
- if (!handled) {
- // If they didn't swipe enough, animate back to resting state
- setDesiredHeight(getDesiredHeight(), true);
- }
- resetState();
- break;
- }
- case MotionEvent.ACTION_DOWN: {
- mDownHeight = getHeight();
- mDownEvent = MotionEvent.obtain(motionEvent);
- // If we are here and care about the return value (i.e. this is not called
- // from onInterceptTouchEvent), then presumably no children view in the panel
- // handles the down event. We'd like to handle future ACTION_MOVE events, so
- // always claim ownership on this event so it doesn't fall through and gets
- // cancelled by the framework.
- return true;
- }
- case MotionEvent.ACTION_MOVE: {
- if (mDownEvent == null) {
- return mMoved;
- }
-
- final float dx = mDownEvent.getRawX() - motionEvent.getRawX();
- final float dy = mDownEvent.getRawY() - motionEvent.getRawY();
- // Don't act if the move is mostly horizontal
- if (Math.abs(dy) > mTouchSlop &&
- (Math.abs(dy) / Math.abs(dx)) > DIRECTION_RATIO) {
- setDesiredHeight((int) (mDownHeight + dy), false);
- mMoved = true;
- if (dy < -mTouchSlop) {
- mMovedDown = true;
- }
- }
- return mMoved;
- }
-
- }
- return mMoved;
- }
-
- private void resetState() {
- mDownEvent = null;
- mDownHeight = -1;
- mMoved = false;
- mMovedDown = false;
- mCanChildViewSwipeDown = false;
- updateViewPager();
- }
- }
-}
-
diff --git a/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java b/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java
deleted file mode 100644
index 7ac7871..0000000
--- a/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.hardware.Camera;
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder;
-import android.net.Uri;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.SafeAsyncTask;
-
-import java.io.FileNotFoundException;
-
-class MmsVideoRecorder extends MediaRecorder {
- private static final float VIDEO_OVERSHOOT_SLOP = .85F;
-
- private static final int BITS_PER_BYTE = 8;
-
- // We think user will expect to be able to record videos at least this long
- private static final long MIN_DURATION_LIMIT_SECONDS = 25;
-
- /** The uri where video is being recorded to */
- private Uri mTempVideoUri;
-
- /** The settings used for video recording */
- private final CamcorderProfile mCamcorderProfile;
-
- public MmsVideoRecorder(final Camera camera, final int cameraIndex, final int orientation,
- final int maxMessageSize)
- throws FileNotFoundException {
- mCamcorderProfile =
- CamcorderProfile.get(cameraIndex, CamcorderProfile.QUALITY_LOW);
- mTempVideoUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(
- ContentType.getExtension(getContentType()));
-
- // The video recorder can sometimes return a file that's larger than the max we
- // say we can handle. Try to handle that overshoot by specifying an 85% limit.
- final long sizeLimit = (long) (maxMessageSize * VIDEO_OVERSHOOT_SLOP);
-
- // The QUALITY_LOW profile might not be low enough to allow for video of a reasonable
- // minimum duration. Adjust a/v bitrates to allow at least MIN_DURATION_LIMIT video
- // to be recorded.
- int audioBitRate = mCamcorderProfile.audioBitRate;
- int videoBitRate = mCamcorderProfile.videoBitRate;
- final double initialDurationLimit = sizeLimit * BITS_PER_BYTE
- / (double) (audioBitRate + videoBitRate);
- if (initialDurationLimit < MIN_DURATION_LIMIT_SECONDS) {
- // Reduce the suggested bitrates. These bitrates are only requests, if implementation
- // can't actually hit these goals it will still record video at higher rate and stop when
- // it hits the size limit.
- final double bitRateAdjustmentFactor = initialDurationLimit / MIN_DURATION_LIMIT_SECONDS;
- audioBitRate *= bitRateAdjustmentFactor;
- videoBitRate *= bitRateAdjustmentFactor;
- }
-
- setCamera(camera);
- setOrientationHint(orientation);
- setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
- setVideoSource(MediaRecorder.VideoSource.CAMERA);
- setOutputFormat(mCamcorderProfile.fileFormat);
- setOutputFile(
- Factory.get().getApplicationContext().getContentResolver().openFileDescriptor(
- mTempVideoUri, "w").getFileDescriptor());
-
- // Copy settings from CamcorderProfile to MediaRecorder
- setAudioEncodingBitRate(audioBitRate);
- setAudioChannels(mCamcorderProfile.audioChannels);
- setAudioEncoder(mCamcorderProfile.audioCodec);
- setAudioSamplingRate(mCamcorderProfile.audioSampleRate);
- setVideoEncodingBitRate(videoBitRate);
- setVideoEncoder(mCamcorderProfile.videoCodec);
- setVideoFrameRate(mCamcorderProfile.videoFrameRate);
- setVideoSize(
- mCamcorderProfile.videoFrameWidth, mCamcorderProfile.videoFrameHeight);
- setMaxFileSize(sizeLimit);
- }
-
- Uri getVideoUri() {
- return mTempVideoUri;
- }
-
- int getVideoWidth() {
- return mCamcorderProfile.videoFrameWidth;
- }
-
- int getVideoHeight() {
- return mCamcorderProfile.videoFrameHeight;
- }
-
- void cleanupTempFile() {
- final Uri tempUri = mTempVideoUri;
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- Factory.get().getApplicationContext().getContentResolver().delete(
- tempUri, null, null);
- }
- });
- mTempVideoUri = null;
- }
-
- String getContentType() {
- if (mCamcorderProfile.fileFormat == OutputFormat.MPEG_4) {
- return ContentType.VIDEO_MP4;
- } else {
- // 3GPP is the only other video format with a constant in OutputFormat
- return ContentType.VIDEO_3GPP;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/PausableChronometer.java b/src/com/android/messaging/ui/mediapicker/PausableChronometer.java
deleted file mode 100644
index dc8f90b..0000000
--- a/src/com/android/messaging/ui/mediapicker/PausableChronometer.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.widget.Chronometer;
-
-import com.android.messaging.ui.PlaybackStateView;
-
-/**
- * A pausable Chronometer implementation. The default Chronometer in Android only stops the UI
- * from updating when you call stop(), but doesn't actually pause it. This implementation adds an
- * additional timestamp that tracks the timespan for the pause and compensate for that.
- */
-public class PausableChronometer extends Chronometer implements PlaybackStateView {
- // Keeps track of how far long the Chronometer has been tracking when it's paused. We'd like
- // to start from this time the next time it's resumed.
- private long mTimeWhenPaused = 0;
-
- public PausableChronometer(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- /**
- * Reset the timer and start counting from zero.
- */
- @Override
- public void restart() {
- reset();
- start();
- }
-
- /**
- * Reset the timer to zero, but don't start it.
- */
- @Override
- public void reset() {
- stop();
- setBase(SystemClock.elapsedRealtime());
- mTimeWhenPaused = 0;
- }
-
- /**
- * Resume the timer after a previous pause.
- */
- @Override
- public void resume() {
- setBase(SystemClock.elapsedRealtime() - mTimeWhenPaused);
- start();
- }
-
- /**
- * Pause the timer.
- */
- @Override
- public void pause() {
- stop();
- mTimeWhenPaused = SystemClock.elapsedRealtime() - getBase();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java
deleted file mode 100644
index 5dc3185..0000000
--- a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.hardware.Camera;
-import android.os.Parcelable;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-
-import java.io.IOException;
-
-/**
- * A software rendered preview surface for the camera. This renders slower and causes more jank, so
- * HardwareCameraPreview is preferred if possible.
- *
- * There is a significant amount of duplication between HardwareCameraPreview and
- * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The
- * implementations of the shared methods are delegated to CameraPreview
- */
-public class SoftwareCameraPreview extends SurfaceView implements CameraPreview.CameraPreviewHost {
- private final CameraPreview mPreview;
-
- public SoftwareCameraPreview(final Context context) {
- super(context);
- mPreview = new CameraPreview(this);
- getHolder().addCallback(new SurfaceHolder.Callback() {
- @Override
- public void surfaceCreated(final SurfaceHolder surfaceHolder) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void surfaceChanged(final SurfaceHolder surfaceHolder, final int format, final int width,
- final int height) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void surfaceDestroyed(final SurfaceHolder surfaceHolder) {
- CameraManager.get().setSurface(null);
- }
- });
- }
-
-
- @Override
- protected void onVisibilityChanged(final View changedView, final int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- mPreview.onVisibilityChanged(visibility);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mPreview.onDetachedFromWindow();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mPreview.onAttachedToWindow();
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- super.onRestoreInstanceState(state);
- mPreview.onRestoreInstanceState();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public View getView() {
- return this;
- }
-
- @Override
- public boolean isValid() {
- return getHolder() != null;
- }
-
- @Override
- public void startPreview(final Camera camera) throws IOException {
- camera.setPreviewDisplay(getHolder());
- }
-
- @Override
- public void onCameraPermissionGranted() {
- mPreview.onCameraPermissionGranted();
- }
-}
-
-
diff --git a/src/com/android/messaging/ui/mediapicker/SoundLevels.java b/src/com/android/messaging/ui/mediapicker/SoundLevels.java
deleted file mode 100644
index 6f4dca6..0000000
--- a/src/com/android/messaging/ui/mediapicker/SoundLevels.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.mediapicker;
-
-import android.animation.ObjectAnimator;
-import android.animation.TimeAnimator;
-import android.animation.TimeAnimator.TimeListener;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import com.android.messaging.R;
-import com.android.messaging.util.LogUtil;
-
-/**
- * This view draws circular sound levels. By default the sound levels are black, unless
- * otherwise defined via {@link #mPrimaryLevelPaint}.
- */
-public class SoundLevels extends View {
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final boolean DEBUG = false;
-
- private boolean mCenterDefined;
- private int mCenterX;
- private int mCenterY;
-
- // Paint for the main level meter, most closely follows the mic.
- private final Paint mPrimaryLevelPaint;
-
- // The minimum size of the levels as a percentage of the max, that is the size when volume is 0.
- private final float mMinimumLevel;
-
- // The minimum size of the levels, that is the size when volume is 0.
- private final float mMinimumLevelSize;
-
- // The maximum size of the levels, that is the size when volume is 100.
- private final float mMaximumLevelSize;
-
- // Generates clock ticks for the animation using the global animation loop.
- private final TimeAnimator mSpeechLevelsAnimator;
-
- private float mCurrentVolume;
-
- // Indicates whether we should be animating the sound level.
- private boolean mIsEnabled;
-
- // Input level is pulled from here.
- private AudioLevelSource mLevelSource;
-
- public SoundLevels(final Context context) {
- this(context, null);
- }
-
- public SoundLevels(final Context context, final AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SoundLevels(final Context context, final AttributeSet attrs, final int defStyle) {
- super(context, attrs, defStyle);
-
- // Safe source, replaced with system one when attached.
- mLevelSource = new AudioLevelSource();
- mLevelSource.setSpeechLevel(0);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SoundLevels,
- defStyle, 0);
-
- mMaximumLevelSize = a.getDimensionPixelOffset(
- R.styleable.SoundLevels_maxLevelRadius, 0);
- mMinimumLevelSize = a.getDimensionPixelOffset(
- R.styleable.SoundLevels_minLevelRadius, 0);
- mMinimumLevel = mMinimumLevelSize / mMaximumLevelSize;
-
- mPrimaryLevelPaint = new Paint();
- mPrimaryLevelPaint.setColor(
- a.getColor(R.styleable.SoundLevels_primaryColor, Color.BLACK));
- mPrimaryLevelPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
-
- a.recycle();
-
- // This animator generates ticks that invalidate the
- // view so that the animation is synced with the global animation loop.
- // TODO: We could probably remove this in favor of using postInvalidateOnAnimation
- // which might improve things further.
- mSpeechLevelsAnimator = new TimeAnimator();
- mSpeechLevelsAnimator.setRepeatCount(ObjectAnimator.INFINITE);
- mSpeechLevelsAnimator.setTimeListener(new TimeListener() {
- @Override
- public void onTimeUpdate(final TimeAnimator animation, final long totalTime,
- final long deltaTime) {
- invalidate();
- }
- });
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- if (!mIsEnabled) {
- return;
- }
-
- if (!mCenterDefined) {
- // One time computation here, because we can't rely on getWidth() to be computed at
- // constructor time or in onFinishInflate :(.
- mCenterX = getWidth() / 2;
- mCenterY = getWidth() / 2;
- mCenterDefined = true;
- }
-
- final int level = mLevelSource.getSpeechLevel();
- // Either ease towards the target level, or decay away from it depending on whether
- // its higher or lower than the current.
- if (level > mCurrentVolume) {
- mCurrentVolume = mCurrentVolume + ((level - mCurrentVolume) / 4);
- } else {
- mCurrentVolume = mCurrentVolume * 0.95f;
- }
-
- final float radius = mMinimumLevel + (1f - mMinimumLevel) * mCurrentVolume / 100;
- mPrimaryLevelPaint.setStyle(Style.FILL);
- canvas.drawCircle(mCenterX, mCenterY, radius * mMaximumLevelSize, mPrimaryLevelPaint);
- }
-
- public void setLevelSource(final AudioLevelSource source) {
- if (DEBUG) {
- Log.d(TAG, "Speech source set.");
- }
- mLevelSource = source;
- }
-
- private void startSpeechLevelsAnimator() {
- if (DEBUG) {
- Log.d(TAG, "startAnimator()");
- }
- if (!mSpeechLevelsAnimator.isStarted()) {
- mSpeechLevelsAnimator.start();
- }
- }
-
- private void stopSpeechLevelsAnimator() {
- if (DEBUG) {
- Log.d(TAG, "stopAnimator()");
- }
- if (mSpeechLevelsAnimator.isStarted()) {
- mSpeechLevelsAnimator.end();
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- stopSpeechLevelsAnimator();
- }
-
- @Override
- public void setEnabled(final boolean enabled) {
- if (enabled == mIsEnabled) {
- return;
- }
- if (DEBUG) {
- Log.d("TAG", "setEnabled: " + enabled);
- }
- super.setEnabled(enabled);
- mIsEnabled = enabled;
- setKeepScreenOn(enabled);
- updateSpeechLevelsAnimatorState();
- }
-
- private void updateSpeechLevelsAnimatorState() {
- if (mIsEnabled) {
- startSpeechLevelsAnimator();
- } else {
- stopSpeechLevelsAnimator();
- }
- }
-
- /**
- * This is required to make the View findable by uiautomator
- */
- @Override
- public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SoundLevels.class.getCanonicalName());
- }
-
- /**
- * Set the alpha level of the sound circles.
- */
- public void setPrimaryColorAlpha(final int alpha) {
- mPrimaryLevelPaint.setAlpha(alpha);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java
deleted file mode 100644
index 92ed3c1..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker.camerafocus;
-
-public interface FocusIndicator {
- public void showStart();
- public void showSuccess(boolean timeout);
- public void showFail(boolean timeout);
- public void clear();
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java
deleted file mode 100644
index e620fc2..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker.camerafocus;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.Camera.Area;
-import android.hardware.Camera.Parameters;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/* A class that handles everything about focus in still picture mode.
- * This also handles the metering area because it is the same as focus area.
- *
- * The test cases:
- * (1) The camera has continuous autofocus. Move the camera. Take a picture when
- * CAF is not in progress.
- * (2) The camera has continuous autofocus. Move the camera. Take a picture when
- * CAF is in progress.
- * (3) The camera has face detection. Point the camera at some faces. Hold the
- * shutter. Release to take a picture.
- * (4) The camera has face detection. Point the camera at some faces. Single tap
- * the shutter to take a picture.
- * (5) The camera has autofocus. Single tap the shutter to take a picture.
- * (6) The camera has autofocus. Hold the shutter. Release to take a picture.
- * (7) The camera has no autofocus. Single tap the shutter and take a picture.
- * (8) The camera has autofocus and supports focus area. Touch the screen to
- * trigger autofocus. Take a picture.
- * (9) The camera has autofocus and supports focus area. Touch the screen to
- * trigger autofocus. Wait until it times out.
- * (10) The camera has no autofocus and supports metering area. Touch the screen
- * to change metering area.
- */
-public class FocusOverlayManager {
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final String TRUE = "true";
- private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
- private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED =
- "auto-whitebalance-lock-supported";
-
- private static final int RESET_TOUCH_FOCUS = 0;
- private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
-
- private int mState = STATE_IDLE;
- private static final int STATE_IDLE = 0; // Focus is not active.
- private static final int STATE_FOCUSING = 1; // Focus is in progress.
- // Focus is in progress and the camera should take a picture after focus finishes.
- private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
- private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
- private static final int STATE_FAIL = 4; // Focus finishes and fails.
-
- private boolean mInitialized;
- private boolean mFocusAreaSupported;
- private boolean mMeteringAreaSupported;
- private boolean mLockAeAwbNeeded;
- private boolean mAeAwbLock;
- private Matrix mMatrix;
-
- private PieRenderer mPieRenderer;
-
- private int mPreviewWidth; // The width of the preview frame layout.
- private int mPreviewHeight; // The height of the preview frame layout.
- private boolean mMirror; // true if the camera is front-facing.
- private int mDisplayOrientation;
- private List<Object> mFocusArea; // focus area in driver format
- private List<Object> mMeteringArea; // metering area in driver format
- private String mFocusMode;
- private String mOverrideFocusMode;
- private Parameters mParameters;
- private Handler mHandler;
- Listener mListener;
-
- public interface Listener {
- public void autoFocus();
- public void cancelAutoFocus();
- public boolean capture();
- public void setFocusParameters();
- }
-
- private class MainHandler extends Handler {
- public MainHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case RESET_TOUCH_FOCUS: {
- cancelAutoFocus();
- break;
- }
- }
- }
- }
-
- public FocusOverlayManager(Listener listener, Looper looper) {
- mHandler = new MainHandler(looper);
- mMatrix = new Matrix();
- mListener = listener;
- }
-
- public void setFocusRenderer(PieRenderer renderer) {
- mPieRenderer = renderer;
- mInitialized = (mMatrix != null);
- }
-
- public void setParameters(Parameters parameters) {
- // parameters can only be null when onConfigurationChanged is called
- // before camera is open. We will just return in this case, because
- // parameters will be set again later with the right parameters after
- // camera is open.
- if (parameters == null) {
- return;
- }
- mParameters = parameters;
- mFocusAreaSupported = isFocusAreaSupported(parameters);
- mMeteringAreaSupported = isMeteringAreaSupported(parameters);
- mLockAeAwbNeeded = (isAutoExposureLockSupported(mParameters) ||
- isAutoWhiteBalanceLockSupported(mParameters));
- }
-
- public void setPreviewSize(int previewWidth, int previewHeight) {
- if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) {
- mPreviewWidth = previewWidth;
- mPreviewHeight = previewHeight;
- setMatrix();
- }
- }
-
- public void setMirror(boolean mirror) {
- mMirror = mirror;
- setMatrix();
- }
-
- public void setDisplayOrientation(int displayOrientation) {
- mDisplayOrientation = displayOrientation;
- setMatrix();
- }
-
- private void setMatrix() {
- if (mPreviewWidth != 0 && mPreviewHeight != 0) {
- Matrix matrix = new Matrix();
- prepareMatrix(matrix, mMirror, mDisplayOrientation,
- mPreviewWidth, mPreviewHeight);
- // In face detection, the matrix converts the driver coordinates to UI
- // coordinates. In tap focus, the inverted matrix converts the UI
- // coordinates to driver coordinates.
- matrix.invert(mMatrix);
- mInitialized = (mPieRenderer != null);
- }
- }
-
- private void lockAeAwbIfNeeded() {
- if (mLockAeAwbNeeded && !mAeAwbLock) {
- mAeAwbLock = true;
- mListener.setFocusParameters();
- }
- }
-
- private void unlockAeAwbIfNeeded() {
- if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) {
- mAeAwbLock = false;
- mListener.setFocusParameters();
- }
- }
-
- public void onShutterDown() {
- if (!mInitialized) {
- return;
- }
-
- boolean autoFocusCalled = false;
- if (needAutoFocusCall()) {
- // Do not focus if touch focus has been triggered.
- if (mState != STATE_SUCCESS && mState != STATE_FAIL) {
- autoFocus();
- autoFocusCalled = true;
- }
- }
-
- if (!autoFocusCalled) {
- lockAeAwbIfNeeded();
- }
- }
-
- public void onShutterUp() {
- if (!mInitialized) {
- return;
- }
-
- if (needAutoFocusCall()) {
- // User releases half-pressed focus key.
- if (mState == STATE_FOCUSING || mState == STATE_SUCCESS
- || mState == STATE_FAIL) {
- cancelAutoFocus();
- }
- }
-
- // Unlock AE and AWB after cancelAutoFocus. Camera API does not
- // guarantee setParameters can be called during autofocus.
- unlockAeAwbIfNeeded();
- }
-
- public void doSnap() {
- if (!mInitialized) {
- return;
- }
-
- // If the user has half-pressed the shutter and focus is completed, we
- // can take the photo right away. If the focus mode is infinity, we can
- // also take the photo.
- if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) {
- capture();
- } else if (mState == STATE_FOCUSING) {
- // Half pressing the shutter (i.e. the focus button event) will
- // already have requested AF for us, so just request capture on
- // focus here.
- mState = STATE_FOCUSING_SNAP_ON_FINISH;
- } else if (mState == STATE_IDLE) {
- // We didn't do focus. This can happen if the user press focus key
- // while the snapshot is still in progress. The user probably wants
- // the next snapshot as soon as possible, so we just do a snapshot
- // without focusing again.
- capture();
- }
- }
-
- public void onAutoFocus(boolean focused, boolean shutterButtonPressed) {
- if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
- // Take the picture no matter focus succeeds or fails. No need
- // to play the AF sound if we're about to play the shutter
- // sound.
- if (focused) {
- mState = STATE_SUCCESS;
- } else {
- mState = STATE_FAIL;
- }
- updateFocusUI();
- capture();
- } else if (mState == STATE_FOCUSING) {
- // This happens when (1) user is half-pressing the focus key or
- // (2) touch focus is triggered. Play the focus tone. Do not
- // take the picture now.
- if (focused) {
- mState = STATE_SUCCESS;
- } else {
- mState = STATE_FAIL;
- }
- updateFocusUI();
- // If this is triggered by touch focus, cancel focus after a
- // while.
- if (mFocusArea != null) {
- mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
- }
- if (shutterButtonPressed) {
- // Lock AE & AWB so users can half-press shutter and recompose.
- lockAeAwbIfNeeded();
- }
- } else if (mState == STATE_IDLE) {
- // User has released the focus key before focus completes.
- // Do nothing.
- }
- }
-
- public void onAutoFocusMoving(boolean moving) {
- if (!mInitialized) {
- return;
- }
-
- // Ignore if we have requested autofocus. This method only handles
- // continuous autofocus.
- if (mState != STATE_IDLE) {
- return;
- }
-
- if (moving) {
- mPieRenderer.showStart();
- } else {
- mPieRenderer.showSuccess(true);
- }
- }
-
- private void initializeFocusAreas(int focusWidth, int focusHeight,
- int x, int y, int previewWidth, int previewHeight) {
- if (mFocusArea == null) {
- mFocusArea = new ArrayList<Object>();
- mFocusArea.add(new Area(new Rect(), 1));
- }
-
- // Convert the coordinates to driver format.
- calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,
- ((Area) mFocusArea.get(0)).rect);
- }
-
- private void initializeMeteringAreas(int focusWidth, int focusHeight,
- int x, int y, int previewWidth, int previewHeight) {
- if (mMeteringArea == null) {
- mMeteringArea = new ArrayList<Object>();
- mMeteringArea.add(new Area(new Rect(), 1));
- }
-
- // Convert the coordinates to driver format.
- // AE area is bigger because exposure is sensitive and
- // easy to over- or underexposure if area is too small.
- calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,
- ((Area) mMeteringArea.get(0)).rect);
- }
-
- public void onSingleTapUp(int x, int y) {
- if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
- return;
- }
-
- // Let users be able to cancel previous touch focus.
- if ((mFocusArea != null) && (mState == STATE_FOCUSING ||
- mState == STATE_SUCCESS || mState == STATE_FAIL)) {
- cancelAutoFocus();
- }
- // Initialize variables.
- int focusWidth = mPieRenderer.getSize();
- int focusHeight = mPieRenderer.getSize();
- if (focusWidth == 0 || mPieRenderer.getWidth() == 0 || mPieRenderer.getHeight() == 0) {
- return;
- }
- int previewWidth = mPreviewWidth;
- int previewHeight = mPreviewHeight;
- // Initialize mFocusArea.
- if (mFocusAreaSupported) {
- initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
- }
- // Initialize mMeteringArea.
- if (mMeteringAreaSupported) {
- initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
- }
-
- // Use margin to set the focus indicator to the touched area.
- mPieRenderer.setFocus(x, y);
-
- // Set the focus area and metering area.
- mListener.setFocusParameters();
- if (mFocusAreaSupported) {
- autoFocus();
- } else { // Just show the indicator in all other cases.
- updateFocusUI();
- // Reset the metering area in 3 seconds.
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
- }
- }
-
- public void onPreviewStarted() {
- mState = STATE_IDLE;
- }
-
- public void onPreviewStopped() {
- // If auto focus was in progress, it would have been stopped.
- mState = STATE_IDLE;
- resetTouchFocus();
- updateFocusUI();
- }
-
- public void onCameraReleased() {
- onPreviewStopped();
- }
-
- private void autoFocus() {
- LogUtil.v(TAG, "Start autofocus.");
- mListener.autoFocus();
- mState = STATE_FOCUSING;
- updateFocusUI();
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- }
-
- private void cancelAutoFocus() {
- LogUtil.v(TAG, "Cancel autofocus.");
-
- // Reset the tap area before calling mListener.cancelAutofocus.
- // Otherwise, focus mode stays at auto and the tap area passed to the
- // driver is not reset.
- resetTouchFocus();
- mListener.cancelAutoFocus();
- mState = STATE_IDLE;
- updateFocusUI();
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- }
-
- private void capture() {
- if (mListener.capture()) {
- mState = STATE_IDLE;
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- }
- }
-
- public String getFocusMode() {
- if (mOverrideFocusMode != null) {
- return mOverrideFocusMode;
- }
- List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
-
- if (mFocusAreaSupported && mFocusArea != null) {
- // Always use autofocus in tap-to-focus.
- mFocusMode = Parameters.FOCUS_MODE_AUTO;
- } else {
- mFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
- }
-
- if (!isSupported(mFocusMode, supportedFocusModes)) {
- // For some reasons, the driver does not support the current
- // focus mode. Fall back to auto.
- if (isSupported(Parameters.FOCUS_MODE_AUTO,
- mParameters.getSupportedFocusModes())) {
- mFocusMode = Parameters.FOCUS_MODE_AUTO;
- } else {
- mFocusMode = mParameters.getFocusMode();
- }
- }
- return mFocusMode;
- }
-
- public List getFocusAreas() {
- return mFocusArea;
- }
-
- public List getMeteringAreas() {
- return mMeteringArea;
- }
-
- public void updateFocusUI() {
- if (!mInitialized) {
- return;
- }
- FocusIndicator focusIndicator = mPieRenderer;
-
- if (mState == STATE_IDLE) {
- if (mFocusArea == null) {
- focusIndicator.clear();
- } else {
- // Users touch on the preview and the indicator represents the
- // metering area. Either focus area is not supported or
- // autoFocus call is not required.
- focusIndicator.showStart();
- }
- } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
- focusIndicator.showStart();
- } else {
- if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) {
- // TODO: check HAL behavior and decide if this can be removed.
- focusIndicator.showSuccess(false);
- } else if (mState == STATE_SUCCESS) {
- focusIndicator.showSuccess(false);
- } else if (mState == STATE_FAIL) {
- focusIndicator.showFail(false);
- }
- }
- }
-
- public void resetTouchFocus() {
- if (!mInitialized) {
- return;
- }
-
- // Put focus indicator to the center. clear reset position
- mPieRenderer.clear();
-
- mFocusArea = null;
- mMeteringArea = null;
- }
-
- private void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple,
- int x, int y, int previewWidth, int previewHeight, Rect rect) {
- int areaWidth = (int) (focusWidth * areaMultiple);
- int areaHeight = (int) (focusHeight * areaMultiple);
- int left = clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);
- int top = clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);
-
- RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);
- mMatrix.mapRect(rectF);
- rectFToRect(rectF, rect);
- }
-
- /* package */ int getFocusState() {
- return mState;
- }
-
- public boolean isFocusCompleted() {
- return mState == STATE_SUCCESS || mState == STATE_FAIL;
- }
-
- public boolean isFocusingSnapOnFinish() {
- return mState == STATE_FOCUSING_SNAP_ON_FINISH;
- }
-
- public void removeMessages() {
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- }
-
- public void overrideFocusMode(String focusMode) {
- mOverrideFocusMode = focusMode;
- }
-
- public void setAeAwbLock(boolean lock) {
- mAeAwbLock = lock;
- }
-
- public boolean getAeAwbLock() {
- return mAeAwbLock;
- }
-
- private boolean needAutoFocusCall() {
- String focusMode = getFocusMode();
- return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY)
- || focusMode.equals(Parameters.FOCUS_MODE_FIXED)
- || focusMode.equals(Parameters.FOCUS_MODE_EDOF));
- }
-
- public static boolean isAutoExposureLockSupported(Parameters params) {
- return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
- }
-
- public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
- return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
- }
-
- public static boolean isSupported(String value, List<String> supported) {
- return supported != null && supported.indexOf(value) >= 0;
- }
-
- public static boolean isMeteringAreaSupported(Parameters params) {
- return params.getMaxNumMeteringAreas() > 0;
- }
-
- public static boolean isFocusAreaSupported(Parameters params) {
- return (params.getMaxNumFocusAreas() > 0
- && isSupported(Parameters.FOCUS_MODE_AUTO,
- params.getSupportedFocusModes()));
- }
-
- public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
- int viewWidth, int viewHeight) {
- // Need mirror for front camera.
- matrix.setScale(mirror ? -1 : 1, 1);
- // This is the value for android.hardware.Camera.setDisplayOrientation.
- matrix.postRotate(displayOrientation);
- // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
- // UI coordinates range from (0, 0) to (width, height).
- matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
- matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
- }
-
- public static int clamp(int x, int min, int max) {
- Assert.isTrue(max >= min);
- if (x > max) {
- return max;
- }
- if (x < min) {
- return min;
- }
- return x;
- }
-
- public static void rectFToRect(RectF rectF, Rect rect) {
- rect.left = Math.round(rectF.left);
- rect.top = Math.round(rectF.top);
- rect.right = Math.round(rectF.right);
- rect.bottom = Math.round(rectF.bottom);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java
deleted file mode 100644
index df6734f..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker.camerafocus;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.view.MotionEvent;
-
-public abstract class OverlayRenderer implements RenderOverlay.Renderer {
-
- private static final String TAG = "CAM OverlayRenderer";
- protected RenderOverlay mOverlay;
-
- protected int mLeft, mTop, mRight, mBottom;
-
- protected boolean mVisible;
-
- public void setVisible(boolean vis) {
- mVisible = vis;
- update();
- }
-
- public boolean isVisible() {
- return mVisible;
- }
-
- // default does not handle touch
- @Override
- public boolean handlesTouch() {
- return false;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent evt) {
- return false;
- }
-
- public abstract void onDraw(Canvas canvas);
-
- public void draw(Canvas canvas) {
- if (mVisible) {
- onDraw(canvas);
- }
- }
-
- @Override
- public void setOverlay(RenderOverlay overlay) {
- mOverlay = overlay;
- }
-
- @Override
- public void layout(int left, int top, int right, int bottom) {
- mLeft = left;
- mRight = right;
- mTop = top;
- mBottom = bottom;
- }
-
- protected Context getContext() {
- if (mOverlay != null) {
- return mOverlay.getContext();
- } else {
- return null;
- }
- }
-
- public int getWidth() {
- return mRight - mLeft;
- }
-
- public int getHeight() {
- return mBottom - mTop;
- }
-
- protected void update() {
- if (mOverlay != null) {
- mOverlay.update();
- }
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java
deleted file mode 100644
index c602852..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker.camerafocus;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Pie menu item
- */
-public class PieItem {
-
- public static interface OnClickListener {
- void onClick(PieItem item);
- }
-
- private Drawable mDrawable;
- private int level;
- private float mCenter;
- private float start;
- private float sweep;
- private float animate;
- private int inner;
- private int outer;
- private boolean mSelected;
- private boolean mEnabled;
- private List<PieItem> mItems;
- private Path mPath;
- private OnClickListener mOnClickListener;
- private float mAlpha;
-
- // Gray out the view when disabled
- private static final float ENABLED_ALPHA = 1;
- private static final float DISABLED_ALPHA = (float) 0.3;
- private boolean mChangeAlphaWhenDisabled = true;
-
- public PieItem(Drawable drawable, int level) {
- mDrawable = drawable;
- this.level = level;
- setAlpha(1f);
- mEnabled = true;
- setAnimationAngle(getAnimationAngle());
- start = -1;
- mCenter = -1;
- }
-
- public boolean hasItems() {
- return mItems != null;
- }
-
- public List<PieItem> getItems() {
- return mItems;
- }
-
- public void addItem(PieItem item) {
- if (mItems == null) {
- mItems = new ArrayList<PieItem>();
- }
- mItems.add(item);
- }
-
- public void setPath(Path p) {
- mPath = p;
- }
-
- public Path getPath() {
- return mPath;
- }
-
- public void setChangeAlphaWhenDisabled (boolean enable) {
- mChangeAlphaWhenDisabled = enable;
- }
-
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- mDrawable.setAlpha((int) (255 * alpha));
- }
-
- public void setAnimationAngle(float a) {
- animate = a;
- }
-
- public float getAnimationAngle() {
- return animate;
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- if (mChangeAlphaWhenDisabled) {
- if (mEnabled) {
- setAlpha(ENABLED_ALPHA);
- } else {
- setAlpha(DISABLED_ALPHA);
- }
- }
- }
-
- public boolean isEnabled() {
- return mEnabled;
- }
-
- public void setSelected(boolean s) {
- mSelected = s;
- }
-
- public boolean isSelected() {
- return mSelected;
- }
-
- public int getLevel() {
- return level;
- }
-
- public void setGeometry(float st, float sw, int inside, int outside) {
- start = st;
- sweep = sw;
- inner = inside;
- outer = outside;
- }
-
- public void setFixedSlice(float center, float sweep) {
- mCenter = center;
- this.sweep = sweep;
- }
-
- public float getCenter() {
- return mCenter;
- }
-
- public float getStart() {
- return start;
- }
-
- public float getStartAngle() {
- return start + animate;
- }
-
- public float getSweep() {
- return sweep;
- }
-
- public int getInnerRadius() {
- return inner;
- }
-
- public int getOuterRadius() {
- return outer;
- }
-
- public void setOnClickListener(OnClickListener listener) {
- mOnClickListener = listener;
- }
-
- public void performClick() {
- if (mOnClickListener != null) {
- mOnClickListener.onClick(this);
- }
- }
-
- public int getIntrinsicWidth() {
- return mDrawable.getIntrinsicWidth();
- }
-
- public int getIntrinsicHeight() {
- return mDrawable.getIntrinsicHeight();
- }
-
- public void setBounds(int left, int top, int right, int bottom) {
- mDrawable.setBounds(left, top, right, bottom);
- }
-
- public void draw(Canvas canvas) {
- mDrawable.draw(canvas);
- }
-
- public void setImageResource(Context context, int resId) {
- Drawable d = context.getResources().getDrawable(resId).mutate();
- d.setBounds(mDrawable.getBounds());
- mDrawable = d;
- setAlpha(mAlpha);
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java
deleted file mode 100644
index ce8ca00..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker.camerafocus;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.os.Handler;
-import android.os.Message;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.Transformation;
-import com.android.messaging.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PieRenderer extends OverlayRenderer
- implements FocusIndicator {
- // Sometimes continuous autofocus starts and stops several times quickly.
- // These states are used to make sure the animation is run for at least some
- // time.
- private volatile int mState;
- private ScaleAnimation mAnimation = new ScaleAnimation();
- private static final int STATE_IDLE = 0;
- private static final int STATE_FOCUSING = 1;
- private static final int STATE_FINISHING = 2;
- private static final int STATE_PIE = 8;
-
- private Runnable mDisappear = new Disappear();
- private Animation.AnimationListener mEndAction = new EndAction();
- private static final int SCALING_UP_TIME = 600;
- private static final int SCALING_DOWN_TIME = 100;
- private static final int DISAPPEAR_TIMEOUT = 200;
- private static final int DIAL_HORIZONTAL = 157;
-
- private static final long PIE_FADE_IN_DURATION = 200;
- private static final long PIE_XFADE_DURATION = 200;
- private static final long PIE_SELECT_FADE_DURATION = 300;
-
- private static final int MSG_OPEN = 0;
- private static final int MSG_CLOSE = 1;
- private static final float PIE_SWEEP = (float) (Math.PI * 2 / 3);
- // geometry
- private Point mCenter;
- private int mRadius;
- private int mRadiusInc;
-
- // the detection if touch is inside a slice is offset
- // inbounds by this amount to allow the selection to show before the
- // finger covers it
- private int mTouchOffset;
-
- private List<PieItem> mItems;
-
- private PieItem mOpenItem;
-
- private Paint mSelectedPaint;
- private Paint mSubPaint;
-
- // touch handling
- private PieItem mCurrentItem;
-
- private Paint mFocusPaint;
- private int mSuccessColor;
- private int mFailColor;
- private int mCircleSize;
- private int mFocusX;
- private int mFocusY;
- private int mCenterX;
- private int mCenterY;
-
- private int mDialAngle;
- private RectF mCircle;
- private RectF mDial;
- private Point mPoint1;
- private Point mPoint2;
- private int mStartAnimationAngle;
- private boolean mFocused;
- private int mInnerOffset;
- private int mOuterStroke;
- private int mInnerStroke;
- private boolean mTapMode;
- private boolean mBlockFocus;
- private int mTouchSlopSquared;
- private Point mDown;
- private boolean mOpening;
- private LinearAnimation mXFade;
- private LinearAnimation mFadeIn;
- private volatile boolean mFocusCancelled;
-
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_OPEN:
- if (mListener != null) {
- mListener.onPieOpened(mCenter.x, mCenter.y);
- }
- break;
- case MSG_CLOSE:
- if (mListener != null) {
- mListener.onPieClosed();
- }
- break;
- }
- }
- };
-
- private PieListener mListener;
-
- public static interface PieListener {
- public void onPieOpened(int centerX, int centerY);
- public void onPieClosed();
- }
-
- public void setPieListener(PieListener pl) {
- mListener = pl;
- }
-
- public PieRenderer(Context context) {
- init(context);
- }
-
- private void init(Context ctx) {
- setVisible(false);
- mItems = new ArrayList<PieItem>();
- Resources res = ctx.getResources();
- mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
- mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
- mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
- mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
- mCenter = new Point(0, 0);
- mSelectedPaint = new Paint();
- mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
- mSelectedPaint.setAntiAlias(true);
- mSubPaint = new Paint();
- mSubPaint.setAntiAlias(true);
- mSubPaint.setColor(Color.argb(200, 250, 230, 128));
- mFocusPaint = new Paint();
- mFocusPaint.setAntiAlias(true);
- mFocusPaint.setColor(Color.WHITE);
- mFocusPaint.setStyle(Paint.Style.STROKE);
- mSuccessColor = Color.GREEN;
- mFailColor = Color.RED;
- mCircle = new RectF();
- mDial = new RectF();
- mPoint1 = new Point();
- mPoint2 = new Point();
- mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset);
- mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
- mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
- mState = STATE_IDLE;
- mBlockFocus = false;
- mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop();
- mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared;
- mDown = new Point();
- }
-
- public boolean showsItems() {
- return mTapMode;
- }
-
- public void addItem(PieItem item) {
- // add the item to the pie itself
- mItems.add(item);
- }
-
- public void removeItem(PieItem item) {
- mItems.remove(item);
- }
-
- public void clearItems() {
- mItems.clear();
- }
-
- public void showInCenter() {
- if ((mState == STATE_PIE) && isVisible()) {
- mTapMode = false;
- show(false);
- } else {
- if (mState != STATE_IDLE) {
- cancelFocus();
- }
- mState = STATE_PIE;
- setCenter(mCenterX, mCenterY);
- mTapMode = true;
- show(true);
- }
- }
-
- public void hide() {
- show(false);
- }
-
- /**
- * guaranteed has center set
- * @param show
- */
- private void show(boolean show) {
- if (show) {
- mState = STATE_PIE;
- // ensure clean state
- mCurrentItem = null;
- mOpenItem = null;
- for (PieItem item : mItems) {
- item.setSelected(false);
- }
- layoutPie();
- fadeIn();
- } else {
- mState = STATE_IDLE;
- mTapMode = false;
- if (mXFade != null) {
- mXFade.cancel();
- }
- }
- setVisible(show);
- mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE);
- }
-
- private void fadeIn() {
- mFadeIn = new LinearAnimation(0, 1);
- mFadeIn.setDuration(PIE_FADE_IN_DURATION);
- mFadeIn.setAnimationListener(new AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- mFadeIn = null;
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- });
- mFadeIn.startNow();
- mOverlay.startAnimation(mFadeIn);
- }
-
- public void setCenter(int x, int y) {
- mCenter.x = x;
- mCenter.y = y;
- // when using the pie menu, align the focus ring
- alignFocus(x, y);
- }
-
- private void layoutPie() {
- int rgap = 2;
- int inner = mRadius + rgap;
- int outer = mRadius + mRadiusInc - rgap;
- int gap = 1;
- layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap);
- }
-
- private void layoutItems(List<PieItem> items, float centerAngle, int inner,
- int outer, int gap) {
- float emptyangle = PIE_SWEEP / 16;
- float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size();
- float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2;
- // check if we have custom geometry
- // first item we find triggers custom sweep for all
- // this allows us to re-use the path
- for (PieItem item : items) {
- if (item.getCenter() >= 0) {
- sweep = item.getSweep();
- break;
- }
- }
- Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap,
- outer, inner, mCenter);
- for (PieItem item : items) {
- // shared between items
- item.setPath(path);
- if (item.getCenter() >= 0) {
- angle = item.getCenter();
- }
- int w = item.getIntrinsicWidth();
- int h = item.getIntrinsicHeight();
- // move views to outer border
- int r = inner + (outer - inner) * 2 / 3;
- int x = (int) (r * Math.cos(angle));
- int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2;
- x = mCenter.x + x - w / 2;
- item.setBounds(x, y, x + w, y + h);
- float itemstart = angle - sweep / 2;
- item.setGeometry(itemstart, sweep, inner, outer);
- if (item.hasItems()) {
- layoutItems(item.getItems(), angle, inner,
- outer + mRadiusInc / 2, gap);
- }
- angle += sweep;
- }
- }
-
- private Path makeSlice(float start, float end, int outer, int inner, Point center) {
- RectF bb =
- new RectF(center.x - outer, center.y - outer, center.x + outer,
- center.y + outer);
- RectF bbi =
- new RectF(center.x - inner, center.y - inner, center.x + inner,
- center.y + inner);
- Path path = new Path();
- path.arcTo(bb, start, end - start, true);
- path.arcTo(bbi, end, start - end);
- path.close();
- return path;
- }
-
- /**
- * converts a
- * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
- * @return skia angle
- */
- private float getDegrees(double angle) {
- return (float) (360 - 180 * angle / Math.PI);
- }
-
- private void startFadeOut() {
- mOverlay.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- deselect();
- show(false);
- mOverlay.setAlpha(1);
- super.onAnimationEnd(animation);
- }
- }).setDuration(PIE_SELECT_FADE_DURATION);
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- float alpha = 1;
- if (mXFade != null) {
- alpha = mXFade.getValue();
- } else if (mFadeIn != null) {
- alpha = mFadeIn.getValue();
- }
- int state = canvas.save();
- if (mFadeIn != null) {
- float sf = 0.9f + alpha * 0.1f;
- canvas.scale(sf, sf, mCenter.x, mCenter.y);
- }
- drawFocus(canvas);
- if (mState == STATE_FINISHING) {
- canvas.restoreToCount(state);
- return;
- }
- if ((mOpenItem == null) || (mXFade != null)) {
- // draw base menu
- for (PieItem item : mItems) {
- drawItem(canvas, item, alpha);
- }
- }
- if (mOpenItem != null) {
- for (PieItem inner : mOpenItem.getItems()) {
- drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
- }
- }
- canvas.restoreToCount(state);
- }
-
- private void drawItem(Canvas canvas, PieItem item, float alpha) {
- if (mState == STATE_PIE) {
- if (item.getPath() != null) {
- if (item.isSelected()) {
- Paint p = mSelectedPaint;
- int state = canvas.save();
- float r = getDegrees(item.getStartAngle());
- canvas.rotate(r, mCenter.x, mCenter.y);
- canvas.drawPath(item.getPath(), p);
- canvas.restoreToCount(state);
- }
- alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
- // draw the item view
- item.setAlpha(alpha);
- item.draw(canvas);
- }
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent evt) {
- float x = evt.getX();
- float y = evt.getY();
- int action = evt.getActionMasked();
- PointF polar = getPolar(x, y, !(mTapMode));
- if (MotionEvent.ACTION_DOWN == action) {
- mDown.x = (int) evt.getX();
- mDown.y = (int) evt.getY();
- mOpening = false;
- if (mTapMode) {
- PieItem item = findItem(polar);
- if ((item != null) && (mCurrentItem != item)) {
- mState = STATE_PIE;
- onEnter(item);
- }
- } else {
- setCenter((int) x, (int) y);
- show(true);
- }
- return true;
- } else if (MotionEvent.ACTION_UP == action) {
- if (isVisible()) {
- PieItem item = mCurrentItem;
- if (mTapMode) {
- item = findItem(polar);
- if (item != null && mOpening) {
- mOpening = false;
- return true;
- }
- }
- if (item == null) {
- mTapMode = false;
- show(false);
- } else if (!mOpening
- && !item.hasItems()) {
- item.performClick();
- startFadeOut();
- mTapMode = false;
- }
- return true;
- }
- } else if (MotionEvent.ACTION_CANCEL == action) {
- if (isVisible() || mTapMode) {
- show(false);
- }
- deselect();
- return false;
- } else if (MotionEvent.ACTION_MOVE == action) {
- if (polar.y < mRadius) {
- if (mOpenItem != null) {
- mOpenItem = null;
- } else {
- deselect();
- }
- return false;
- }
- PieItem item = findItem(polar);
- boolean moved = hasMoved(evt);
- if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
- // only select if we didn't just open or have moved past slop
- mOpening = false;
- if (moved) {
- // switch back to swipe mode
- mTapMode = false;
- }
- onEnter(item);
- }
- }
- return false;
- }
-
- private boolean hasMoved(MotionEvent e) {
- return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x)
- + (e.getY() - mDown.y) * (e.getY() - mDown.y);
- }
-
- /**
- * enter a slice for a view
- * updates model only
- * @param item
- */
- private void onEnter(PieItem item) {
- if (mCurrentItem != null) {
- mCurrentItem.setSelected(false);
- }
- if (item != null && item.isEnabled()) {
- item.setSelected(true);
- mCurrentItem = item;
- if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) {
- openCurrentItem();
- }
- } else {
- mCurrentItem = null;
- }
- }
-
- private void deselect() {
- if (mCurrentItem != null) {
- mCurrentItem.setSelected(false);
- }
- if (mOpenItem != null) {
- mOpenItem = null;
- }
- mCurrentItem = null;
- }
-
- private void openCurrentItem() {
- if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
- mCurrentItem.setSelected(false);
- mOpenItem = mCurrentItem;
- mOpening = true;
- mXFade = new LinearAnimation(1, 0);
- mXFade.setDuration(PIE_XFADE_DURATION);
- mXFade.setAnimationListener(new AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- mXFade = null;
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- });
- mXFade.startNow();
- mOverlay.startAnimation(mXFade);
- }
- }
-
- private PointF getPolar(float x, float y, boolean useOffset) {
- PointF res = new PointF();
- // get angle and radius from x/y
- res.x = (float) Math.PI / 2;
- x = x - mCenter.x;
- y = mCenter.y - y;
- res.y = (float) Math.sqrt(x * x + y * y);
- if (x != 0) {
- res.x = (float) Math.atan2(y, x);
- if (res.x < 0) {
- res.x = (float) (2 * Math.PI + res.x);
- }
- }
- res.y = res.y + (useOffset ? mTouchOffset : 0);
- return res;
- }
-
- /**
- * @param polar x: angle, y: dist
- * @return the item at angle/dist or null
- */
- private PieItem findItem(PointF polar) {
- // find the matching item:
- List<PieItem> items = (mOpenItem != null) ? mOpenItem.getItems() : mItems;
- for (PieItem item : items) {
- if (inside(polar, item)) {
- return item;
- }
- }
- return null;
- }
-
- private boolean inside(PointF polar, PieItem item) {
- return (item.getInnerRadius() < polar.y)
- && (item.getStartAngle() < polar.x)
- && (item.getStartAngle() + item.getSweep() > polar.x)
- && (!mTapMode || (item.getOuterRadius() > polar.y));
- }
-
- @Override
- public boolean handlesTouch() {
- return true;
- }
-
- // focus specific code
-
- public void setBlockFocus(boolean blocked) {
- mBlockFocus = blocked;
- if (blocked) {
- clear();
- }
- }
-
- public void setFocus(int x, int y) {
- mFocusX = x;
- mFocusY = y;
- setCircle(mFocusX, mFocusY);
- }
-
- public void alignFocus(int x, int y) {
- mOverlay.removeCallbacks(mDisappear);
- mAnimation.cancel();
- mAnimation.reset();
- mFocusX = x;
- mFocusY = y;
- mDialAngle = DIAL_HORIZONTAL;
- setCircle(x, y);
- mFocused = false;
- }
-
- public int getSize() {
- return 2 * mCircleSize;
- }
-
- private int getRandomRange() {
- return (int) (-60 + 120 * Math.random());
- }
-
- @Override
- public void layout(int l, int t, int r, int b) {
- super.layout(l, t, r, b);
- mCenterX = (r - l) / 2;
- mCenterY = (b - t) / 2;
- mFocusX = mCenterX;
- mFocusY = mCenterY;
- setCircle(mFocusX, mFocusY);
- if (isVisible() && mState == STATE_PIE) {
- setCenter(mCenterX, mCenterY);
- layoutPie();
- }
- }
-
- private void setCircle(int cx, int cy) {
- mCircle.set(cx - mCircleSize, cy - mCircleSize,
- cx + mCircleSize, cy + mCircleSize);
- mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset,
- cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset);
- }
-
- public void drawFocus(Canvas canvas) {
- if (mBlockFocus) {
- return;
- }
- mFocusPaint.setStrokeWidth(mOuterStroke);
- canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint);
- if (mState == STATE_PIE) {
- return;
- }
- int color = mFocusPaint.getColor();
- if (mState == STATE_FINISHING) {
- mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor);
- }
- mFocusPaint.setStrokeWidth(mInnerStroke);
- drawLine(canvas, mDialAngle, mFocusPaint);
- drawLine(canvas, mDialAngle + 45, mFocusPaint);
- drawLine(canvas, mDialAngle + 180, mFocusPaint);
- drawLine(canvas, mDialAngle + 225, mFocusPaint);
- canvas.save();
- // rotate the arc instead of its offset to better use framework's shape caching
- canvas.rotate(mDialAngle, mFocusX, mFocusY);
- canvas.drawArc(mDial, 0, 45, false, mFocusPaint);
- canvas.drawArc(mDial, 180, 45, false, mFocusPaint);
- canvas.restore();
- mFocusPaint.setColor(color);
- }
-
- private void drawLine(Canvas canvas, int angle, Paint p) {
- convertCart(angle, mCircleSize - mInnerOffset, mPoint1);
- convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2);
- canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY,
- mPoint2.x + mFocusX, mPoint2.y + mFocusY, p);
- }
-
- private static void convertCart(int angle, int radius, Point out) {
- double a = 2 * Math.PI * (angle % 360) / 360;
- out.x = (int) (radius * Math.cos(a) + 0.5);
- out.y = (int) (radius * Math.sin(a) + 0.5);
- }
-
- @Override
- public void showStart() {
- if (mState == STATE_PIE) {
- return;
- }
- cancelFocus();
- mStartAnimationAngle = 67;
- int range = getRandomRange();
- startAnimation(SCALING_UP_TIME,
- false, mStartAnimationAngle, mStartAnimationAngle + range);
- mState = STATE_FOCUSING;
- }
-
- @Override
- public void showSuccess(boolean timeout) {
- if (mState == STATE_FOCUSING) {
- startAnimation(SCALING_DOWN_TIME,
- timeout, mStartAnimationAngle);
- mState = STATE_FINISHING;
- mFocused = true;
- }
- }
-
- @Override
- public void showFail(boolean timeout) {
- if (mState == STATE_FOCUSING) {
- startAnimation(SCALING_DOWN_TIME,
- timeout, mStartAnimationAngle);
- mState = STATE_FINISHING;
- mFocused = false;
- }
- }
-
- private void cancelFocus() {
- mFocusCancelled = true;
- mOverlay.removeCallbacks(mDisappear);
- if (mAnimation != null) {
- mAnimation.cancel();
- }
- mFocusCancelled = false;
- mFocused = false;
- mState = STATE_IDLE;
- }
-
- @Override
- public void clear() {
- if (mState == STATE_PIE) {
- return;
- }
- cancelFocus();
- mOverlay.post(mDisappear);
- }
-
- private void startAnimation(long duration, boolean timeout,
- float toScale) {
- startAnimation(duration, timeout, mDialAngle,
- toScale);
- }
-
- private void startAnimation(long duration, boolean timeout,
- float fromScale, float toScale) {
- setVisible(true);
- mAnimation.reset();
- mAnimation.setDuration(duration);
- mAnimation.setScale(fromScale, toScale);
- mAnimation.setAnimationListener(timeout ? mEndAction : null);
- mOverlay.startAnimation(mAnimation);
- update();
- }
-
- private class EndAction implements Animation.AnimationListener {
- @Override
- public void onAnimationEnd(Animation animation) {
- // Keep the focus indicator for some time.
- if (!mFocusCancelled) {
- mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT);
- }
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
-
- @Override
- public void onAnimationStart(Animation animation) {
- }
- }
-
- private class Disappear implements Runnable {
- @Override
- public void run() {
- if (mState == STATE_PIE) {
- return;
- }
- setVisible(false);
- mFocusX = mCenterX;
- mFocusY = mCenterY;
- mState = STATE_IDLE;
- setCircle(mFocusX, mFocusY);
- mFocused = false;
- }
- }
-
- private class ScaleAnimation extends Animation {
- private float mFrom = 1f;
- private float mTo = 1f;
-
- public ScaleAnimation() {
- setFillAfter(true);
- }
-
- public void setScale(float from, float to) {
- mFrom = from;
- mTo = to;
- }
-
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- mDialAngle = (int) (mFrom + (mTo - mFrom) * interpolatedTime);
- }
- }
-
-
- private class LinearAnimation extends Animation {
- private float mFrom;
- private float mTo;
- private float mValue;
-
- public LinearAnimation(float from, float to) {
- setFillAfter(true);
- setInterpolator(new LinearInterpolator());
- mFrom = from;
- mTo = to;
- }
-
- public float getValue() {
- return mValue;
- }
-
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- mValue = (mFrom + (mTo - mFrom) * interpolatedTime);
- }
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt b/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt
deleted file mode 100644
index ed4e783..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-The files in this package were copied from the android-4.4.4_r1 branch of ASOP from the folders
-com/android/camera/ and com/android/camera/ui from files with the same name. Some modifications
-have been made to remove unneeded features and adjust to our needs. \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java b/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java
deleted file mode 100644
index 95cddc4..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker.camerafocus;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class RenderOverlay extends FrameLayout {
-
- interface Renderer {
-
- public boolean handlesTouch();
- public boolean onTouchEvent(MotionEvent evt);
- public void setOverlay(RenderOverlay overlay);
- public void layout(int left, int top, int right, int bottom);
- public void draw(Canvas canvas);
-
- }
-
- private RenderView mRenderView;
- private List<Renderer> mClients;
-
- // reverse list of touch clients
- private List<Renderer> mTouchClients;
- private int[] mPosition = new int[2];
-
- public RenderOverlay(Context context, AttributeSet attrs) {
- super(context, attrs);
- mRenderView = new RenderView(context);
- addView(mRenderView, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- mClients = new ArrayList<Renderer>(10);
- mTouchClients = new ArrayList<Renderer>(10);
- setWillNotDraw(false);
-
- addRenderer(new PieRenderer(context));
- }
-
- public PieRenderer getPieRenderer() {
- for (Renderer renderer : mClients) {
- if (renderer instanceof PieRenderer) {
- return (PieRenderer) renderer;
- }
- }
- return null;
- }
-
- public void addRenderer(Renderer renderer) {
- mClients.add(renderer);
- renderer.setOverlay(this);
- if (renderer.handlesTouch()) {
- mTouchClients.add(0, renderer);
- }
- renderer.layout(getLeft(), getTop(), getRight(), getBottom());
- }
-
- public void addRenderer(int pos, Renderer renderer) {
- mClients.add(pos, renderer);
- renderer.setOverlay(this);
- renderer.layout(getLeft(), getTop(), getRight(), getBottom());
- }
-
- public void remove(Renderer renderer) {
- mClients.remove(renderer);
- renderer.setOverlay(null);
- }
-
- public int getClientSize() {
- return mClients.size();
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent m) {
- return false;
- }
-
- public boolean directDispatchTouch(MotionEvent m, Renderer target) {
- mRenderView.setTouchTarget(target);
- boolean res = super.dispatchTouchEvent(m);
- mRenderView.setTouchTarget(null);
- return res;
- }
-
- private void adjustPosition() {
- getLocationInWindow(mPosition);
- }
-
- public int getWindowPositionX() {
- return mPosition[0];
- }
-
- public int getWindowPositionY() {
- return mPosition[1];
- }
-
- public void update() {
- mRenderView.invalidate();
- }
-
- private class RenderView extends View {
-
- private Renderer mTouchTarget;
-
- public RenderView(Context context) {
- super(context);
- setWillNotDraw(false);
- }
-
- public void setTouchTarget(Renderer target) {
- mTouchTarget = target;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent evt) {
- if (mTouchTarget != null) {
- return mTouchTarget.onTouchEvent(evt);
- }
- if (mTouchClients != null) {
- boolean res = false;
- for (Renderer client : mTouchClients) {
- res |= client.onTouchEvent(evt);
- }
- return res;
- }
- return false;
- }
-
- @Override
- public void onLayout(boolean changed, int left, int top, int right, int bottom) {
- adjustPosition();
- super.onLayout(changed, left, top, right, bottom);
- if (mClients == null) {
- return;
- }
- for (Renderer renderer : mClients) {
- renderer.layout(left, top, right, bottom);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (mClients == null) {
- return;
- }
- boolean redraw = false;
- for (Renderer renderer : mClients) {
- renderer.draw(canvas);
- redraw = redraw || ((OverlayRenderer) renderer).isVisible();
- }
- if (redraw) {
- invalidate();
- }
- }
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java
deleted file mode 100644
index d139a38..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.photoviewer;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.support.rastermill.FrameSequenceDrawable;
-import android.support.v4.content.AsyncTaskLoader;
-
-import com.android.ex.photo.PhotoViewController;
-import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface;
-import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.util.ImageUtils;
-
-/**
- * Loader for the bitmap of a photo.
- */
-public class BuglePhotoBitmapLoader extends AsyncTaskLoader<BitmapResult>
- implements PhotoBitmapLoaderInterface {
- private String mPhotoUri;
- private ImageResource mImageResource;
- // The drawable that is currently "in use" and being presented to the user. This drawable
- // should never exist without the image resource backing it.
- private Drawable mDrawable;
-
- public BuglePhotoBitmapLoader(Context context, String photoUri) {
- super(context);
- mPhotoUri = photoUri;
- }
-
- @Override
- public void setPhotoUri(String photoUri) {
- mPhotoUri = photoUri;
- }
-
- @Override
- public BitmapResult loadInBackground() {
- final BitmapResult result = new BitmapResult();
- final Context context = getContext();
- if (context != null && mPhotoUri != null) {
- final ImageRequestDescriptor descriptor =
- new UriImageRequestDescriptor(Uri.parse(mPhotoUri),
- PhotoViewController.sMaxPhotoSize, PhotoViewController.sMaxPhotoSize,
- true /* allowCompression */, false /* isStatic */,
- false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- final MediaRequest<ImageResource> imageRequest =
- descriptor.buildSyncMediaRequest(context);
- final ImageResource imageResource =
- MediaResourceManager.get().requestMediaResourceSync(imageRequest);
- if (imageResource != null) {
- setImageResource(imageResource);
- result.status = BitmapResult.STATUS_SUCCESS;
- result.drawable = mImageResource.getDrawable(context.getResources());
- } else {
- releaseImageResource();
- result.status = BitmapResult.STATUS_EXCEPTION;
- }
- } else {
- result.status = BitmapResult.STATUS_EXCEPTION;
- }
- return result;
- }
-
- /**
- * Called when there is new data to deliver to the client. The super class will take care of
- * delivering it; the implementation here just adds a little more logic.
- */
- @Override
- public void deliverResult(BitmapResult result) {
- final Drawable drawable = result != null ? result.drawable : null;
- if (isReset()) {
- // An async query came in while the loader is stopped. We don't need the result.
- releaseDrawable(drawable);
- return;
- }
-
- // We are now going to display this drawable so set to mDrawable
- mDrawable = drawable;
-
- if (isStarted()) {
- // If the Loader is currently started, we can immediately deliver its results.
- super.deliverResult(result);
- }
- }
-
- /**
- * Handles a request to start the Loader.
- */
- @Override
- protected void onStartLoading() {
- if (mDrawable != null) {
- // If we currently have a result available, deliver it
- // immediately.
- final BitmapResult result = new BitmapResult();
- result.status = BitmapResult.STATUS_SUCCESS;
- result.drawable = mDrawable;
- deliverResult(result);
- }
-
- if (takeContentChanged() || (mImageResource == null)) {
- // If the data has changed since the last time it was loaded
- // or is not currently available, start a load.
- forceLoad();
- }
- }
-
- /**
- * Handles a request to stop the Loader.
- */
- @Override
- protected void onStopLoading() {
- // Attempt to cancel the current load task if possible.
- cancelLoad();
- }
-
- /**
- * Handles a request to cancel a load.
- */
- @Override
- public void onCanceled(BitmapResult result) {
- super.onCanceled(result);
-
- // At this point we can release the resources associated with 'drawable' if needed.
- if (result != null) {
- releaseDrawable(result.drawable);
- }
- }
-
- /**
- * Handles a request to completely reset the Loader.
- */
- @Override
- protected void onReset() {
- super.onReset();
-
- // Ensure the loader is stopped
- onStopLoading();
-
- releaseImageResource();
- }
-
- private void releaseDrawable(Drawable drawable) {
- if (drawable != null && drawable instanceof FrameSequenceDrawable
- && !((FrameSequenceDrawable) drawable).isDestroyed()) {
- ((FrameSequenceDrawable) drawable).destroy();
- }
-
- }
-
- private void setImageResource(final ImageResource resource) {
- if (mImageResource != resource) {
- // Clear out any information for what is currently used
- releaseImageResource();
- mImageResource = resource;
- // No need to add ref since a ref is already reserved as a result of
- // requestMediaResourceSync.
- }
- }
-
- private void releaseImageResource() {
- // If we are getting rid of the imageResource backing the drawable, we must also
- // destroy the drawable before releasing it.
- releaseDrawable(mDrawable);
- mDrawable = null;
-
- if (mImageResource != null) {
- mImageResource.release();
- }
- mImageResource = null;
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java
deleted file mode 100644
index 52498c7..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.photoviewer;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.support.v4.app.FragmentManager;
-
-import com.android.ex.photo.adapters.PhotoPagerAdapter;
-import com.android.ex.photo.fragments.PhotoViewFragment;
-
-public class BuglePhotoPageAdapter extends PhotoPagerAdapter {
-
- public BuglePhotoPageAdapter(Context context, FragmentManager fm, Cursor c, float maxScale,
- boolean thumbsFullScreen) {
- super(context, fm, c, maxScale, thumbsFullScreen);
- }
-
- @Override
- protected PhotoViewFragment createPhotoViewFragment(Intent intent, int position,
- boolean onlyShowSpinner) {
- return BuglePhotoViewFragment.newInstance(intent, position, onlyShowSpinner);
- }
-}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java
deleted file mode 100644
index 1924a96..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.photoviewer;
-
-import com.android.ex.photo.PhotoViewActivity;
-import com.android.ex.photo.PhotoViewController;
-
-/**
- * Activity to display the conversation images in full-screen. Most of the customization is in
- * {@link BuglePhotoViewController}.
- */
-public class BuglePhotoViewActivity extends PhotoViewActivity {
- @Override
- public PhotoViewController createController() {
- return new BuglePhotoViewController(this);
- }
-}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java
deleted file mode 100644
index eb39886..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.photoviewer;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.content.Loader;
-import android.text.TextUtils;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.widget.ShareActionProvider;
-import android.widget.Toast;
-
-import com.android.ex.photo.PhotoViewController;
-import com.android.ex.photo.adapters.PhotoPagerAdapter;
-import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.ConversationImagePartsView.PhotoViewQuery;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.ui.conversation.ConversationFragment;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Customizations for the photoviewer to display conversation images in full screen.
- */
-public class BuglePhotoViewController extends PhotoViewController {
- private ShareActionProvider mShareActionProvider;
- private MenuItem mShareItem;
- private MenuItem mSaveItem;
-
- public BuglePhotoViewController(final ActivityInterface activity) {
- super(activity);
- }
-
- @Override
- public Loader<BitmapResult> onCreateBitmapLoader(
- final int id, final Bundle args, final String uri) {
- switch (id) {
- case BITMAP_LOADER_AVATAR:
- case BITMAP_LOADER_THUMBNAIL:
- case BITMAP_LOADER_PHOTO:
- return new BuglePhotoBitmapLoader(getActivity().getContext(), uri);
- default:
- LogUtil.e(LogUtil.BUGLE_TAG,
- "Photoviewer unable to open bitmap loader with unknown id: " + id);
- return null;
- }
- }
-
- @Override
- public void updateActionBar() {
- final Cursor cursor = getCursorAtProperPosition();
-
- if (mSaveItem == null || cursor == null) {
- // Load not finished, called from framework code before ready
- return;
- }
- // Show the name as the title
- mActionBarTitle = cursor.getString(PhotoViewQuery.INDEX_SENDER_FULL_NAME);
- if (TextUtils.isEmpty(mActionBarTitle)) {
- // If the name is not known, fall back to the phone number
- mActionBarTitle = cursor.getString(PhotoViewQuery.INDEX_DISPLAY_DESTINATION);
- }
-
- // Show the timestamp as the subtitle
- final long receivedTimestamp = cursor.getLong(PhotoViewQuery.INDEX_RECEIVED_TIMESTAMP);
- mActionBarSubtitle = Dates.getMessageTimeString(receivedTimestamp).toString();
-
- setActionBarTitles(getActivity().getActionBarInterface());
- mSaveItem.setVisible(!isTempFile());
-
- updateShareActionProvider();
- }
-
- private void updateShareActionProvider() {
- final PhotoPagerAdapter adapter = getAdapter();
- final Cursor cursor = getCursorAtProperPosition();
- if (mShareActionProvider == null || mShareItem == null || adapter == null ||
- cursor == null) {
- // Not enough stuff loaded to update the share action
- return;
- }
- final String photoUri = adapter.getPhotoUri(cursor);
- if (isTempFile()) {
- mShareItem.setVisible(false);
- return;
- }
- final String contentType = adapter.getContentType(cursor);
-
- final Intent shareIntent = new Intent();
- shareIntent.setAction(Intent.ACTION_SEND);
- shareIntent.setType(contentType);
- shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(photoUri));
- mShareActionProvider.setShareIntent(shareIntent);
- mShareItem.setVisible(true);
- }
-
- /**
- * Checks whether the current photo is a temp file. A temp file can be deleted at any time, so
- * we need to disable share and save options because the file may no longer be there.
- */
- private boolean isTempFile() {
- final Cursor cursor = getCursorAtProperPosition();
- final Uri photoUri = Uri.parse(getAdapter().getPhotoUri(cursor));
- return MediaScratchFileProvider.isMediaScratchSpaceUri(photoUri);
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- ((Activity) getActivity()).getMenuInflater().inflate(R.menu.photo_view_menu, menu);
-
- // Get the ShareActionProvider
- mShareItem = menu.findItem(R.id.action_share);
- mShareActionProvider = (ShareActionProvider) mShareItem.getActionProvider();
- updateShareActionProvider();
-
- mSaveItem = menu.findItem(R.id.action_save);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(final Menu menu) {
- return !mIsEmpty;
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (item.getItemId() == R.id.action_save) {
- if (OsUtil.hasStoragePermission()) {
- final PhotoPagerAdapter adapter = getAdapter();
- final Cursor cursor = getCursorAtProperPosition();
- if (cursor == null) {
- final Context context = getActivity().getContext();
- final String error = context.getResources().getQuantityString(
- R.plurals.attachment_save_error, 1, 1);
- Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
- return true;
- }
- final String photoUri = adapter.getPhotoUri(cursor);
- new ConversationFragment.SaveAttachmentTask(((Activity) getActivity()),
- Uri.parse(photoUri), adapter.getContentType(cursor)).executeOnThreadPool();
- } else {
- ((Activity)getActivity()).requestPermissions(
- new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);
- }
- return true;
- } else {
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public PhotoPagerAdapter createPhotoPagerAdapter(final Context context,
- final FragmentManager fm, final Cursor c, final float maxScale) {
- return new BuglePhotoPageAdapter(context, fm, c, maxScale, mDisplayThumbsFullScreen);
- }
-}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java
deleted file mode 100644
index 698c510..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.ui.photoviewer;
-
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.support.rastermill.FrameSequenceDrawable;
-import android.support.v4.content.Loader;
-
-import com.android.ex.photo.PhotoViewCallbacks;
-import com.android.ex.photo.fragments.PhotoViewFragment;
-import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
-
-public class BuglePhotoViewFragment extends PhotoViewFragment {
-
- /** Public no-arg constructor for allowing the framework to handle orientation changes */
- public BuglePhotoViewFragment() {
- // Do nothing.
- }
-
- public static PhotoViewFragment newInstance(Intent intent, int position,
- boolean onlyShowSpinner) {
- final PhotoViewFragment f = new BuglePhotoViewFragment();
- initializeArguments(intent, position, onlyShowSpinner, f);
- return f;
- }
-
- @Override
- public void onLoadFinished(Loader<BitmapResult> loader, BitmapResult result) {
- super.onLoadFinished(loader, result);
- // Need to check for the first time when we load the photos
- if (PhotoViewCallbacks.BITMAP_LOADER_PHOTO == loader.getId()
- && result.status == BitmapResult.STATUS_SUCCESS
- && mCallback.isFragmentActive(this)) {
- startGif();
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- startGif();
- }
-
- @Override
- public void onPause() {
- stopGif();
- super.onPause();
- }
-
- @Override
- public void onViewActivated() {
- super.onViewActivated();
- startGif();
- }
-
- @Override
- public void resetViews() {
- super.resetViews();
- stopGif();
- }
-
- private void stopGif() {
- final Drawable drawable = getDrawable();
- if (drawable != null && drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).stop();
- }
- }
-
- private void startGif() {
- final Drawable drawable = getDrawable();
- if (drawable != null && drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).start();
- }
- }
-}
diff --git a/src/com/android/messaging/util/AccessibilityUtil.java b/src/com/android/messaging/util/AccessibilityUtil.java
deleted file mode 100644
index f6c64a9..0000000
--- a/src/com/android/messaging/util/AccessibilityUtil.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.support.v4.view.accessibility.AccessibilityEventCompat;
-import android.support.v4.view.accessibility.AccessibilityRecordCompat;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-
-import javax.annotation.Nullable;
-
-public class AccessibilityUtil {
- public static String sContentDescriptionDivider;
-
- public static boolean isTouchExplorationEnabled(final Context context) {
- final AccessibilityManager accessibilityManager = (AccessibilityManager)
- context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- return accessibilityManager.isTouchExplorationEnabled();
- }
-
- public static StringBuilder appendContentDescription(final Context context,
- final StringBuilder contentDescription, final String val) {
- if (sContentDescriptionDivider == null) {
- sContentDescriptionDivider =
- context.getResources().getString(R.string.enumeration_comma);
- }
- if (contentDescription.length() != 0) {
- contentDescription.append(sContentDescriptionDivider);
- }
- contentDescription.append(val);
- return contentDescription;
- }
-
- public static void announceForAccessibilityCompat(
- final View view, @Nullable final AccessibilityManager accessibilityManager,
- final int textResourceId) {
- final String text = Factory.get().getApplicationContext().getResources().getString(
- textResourceId);
- announceForAccessibilityCompat(view, accessibilityManager, text);
- }
-
- public static void announceForAccessibilityCompat(
- final View view, @Nullable AccessibilityManager accessibilityManager,
- final CharSequence text) {
- final Context context = view.getContext().getApplicationContext();
- if (accessibilityManager == null) {
- accessibilityManager = (AccessibilityManager) context.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- }
-
- if (!accessibilityManager.isEnabled()) {
- return;
- }
-
- // Jelly Bean added support for speaking text verbatim
- final int eventType = OsUtil.isAtLeastJB() ? AccessibilityEvent.TYPE_ANNOUNCEMENT
- : AccessibilityEvent.TYPE_VIEW_FOCUSED;
-
- // Construct an accessibility event with the minimum recommended
- // attributes. An event without a class name or package may be dropped.
- final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- event.getText().add(text);
- event.setEnabled(view.isEnabled());
- event.setClassName(view.getClass().getName());
- event.setPackageName(context.getPackageName());
-
- // JellyBean MR1 requires a source view to set the window ID.
- final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
- record.setSource(view);
-
- // Sends the event directly through the accessibility manager. If we only supported SDK 14+
- // we could have done:
- // getParent().requestSendAccessibilityEvent(this, event);
- accessibilityManager.sendAccessibilityEvent(event);
- }
-
- /**
- * Check to see if the current layout is Right-to-Left. This check is only supported for
- * API 17+.
- * For earlier versions, this method will just return false.
- * @return boolean Boolean indicating whether the currently locale is RTL.
- */
- public static boolean isLayoutRtl(final View view) {
- if (OsUtil.isAtLeastJB_MR1()) {
- return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
- } else {
- return false;
- }
- }
-
- public static String getVocalizedPhoneNumber(final Resources res, final String phoneNumber) {
- if (TextUtils.isEmpty(phoneNumber)) {
- return "";
- }
- final StringBuilder vocalizedPhoneNumber = new StringBuilder();
- for (final char c : phoneNumber.toCharArray()) {
- getVocalizedNumber(res, c, vocalizedPhoneNumber);
- }
- return vocalizedPhoneNumber.toString();
- }
-
- public static void getVocalizedNumber(final Resources res, final char c,
- final StringBuilder builder) {
- switch (c) {
- case '0':
- builder.append(res.getString(R.string.content_description_for_number_zero));
- builder.append(" ");
- return;
- case '1':
- builder.append(res.getString(R.string.content_description_for_number_one));
- builder.append(" ");
- return;
- case '2':
- builder.append(res.getString(R.string.content_description_for_number_two));
- builder.append(" ");
- return;
- case '3':
- builder.append(res.getString(R.string.content_description_for_number_three));
- builder.append(" ");
- return;
- case '4':
- builder.append(res.getString(R.string.content_description_for_number_four));
- builder.append(" ");
- return;
- case '5':
- builder.append(res.getString(R.string.content_description_for_number_five));
- builder.append(" ");
- return;
- case '6':
- builder.append(res.getString(R.string.content_description_for_number_six));
- builder.append(" ");
- return;
- case '7':
- builder.append(res.getString(R.string.content_description_for_number_seven));
- builder.append(" ");
- return;
- case '8':
- builder.append(res.getString(R.string.content_description_for_number_eight));
- builder.append(" ");
- return;
- case '9':
- builder.append(res.getString(R.string.content_description_for_number_nine));
- builder.append(" ");
- return;
- default:
- builder.append(c);
- return;
- }
- }
-}
diff --git a/src/com/android/messaging/util/Assert.java b/src/com/android/messaging/util/Assert.java
deleted file mode 100644
index 437965c..0000000
--- a/src/com/android/messaging/util/Assert.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.os.Looper;
-
-import java.util.Arrays;
-
-public final class Assert {
- public static @interface RunsOnMainThread {}
- public static @interface DoesNotRunOnMainThread {}
- public static @interface RunsOnAnyThread {}
-
- private static final String TEST_THREAD_SUBSTRING = "test";
-
- private static boolean sIsEngBuild;
- private static boolean sShouldCrash;
-
- // Private constructor so no one creates this class.
- private Assert() {
- }
-
- // The proguard rules will strip this method out on user/userdebug builds.
- // If you change the method signature you MUST edit proguard-release.flags.
- private static void setIfEngBuild() {
- sShouldCrash = sIsEngBuild = true;
- }
-
- private static void refreshGservices(final BugleGservices gservices) {
- sShouldCrash = sIsEngBuild;
- if (!sShouldCrash) {
- sShouldCrash = gservices.getBoolean(
- BugleGservicesKeys.ASSERTS_FATAL,
- BugleGservicesKeys.ASSERTS_FATAL_DEFAULT);
- }
- }
-
- // Static initializer block to find out if we're running an eng or
- // release build.
- static {
- setIfEngBuild();
- }
-
- // This is called from FactoryImpl once the Gservices class is initialized.
- public static void initializeGservices (final BugleGservices gservices) {
- gservices.registerForChanges(new Runnable() {
- @Override
- public void run() {
- refreshGservices(gservices);
- }
- });
- refreshGservices(gservices);
- }
-
- /**
- * Halt execution if this is not an eng build.
- * <p>Intended for use in code paths that should be run only for tests and never on
- * a real build.
- * <p>Note that this will crash on a user build even though asserts don't normally
- * crash on a user build.
- */
- public static void isEngBuild() {
- isTrueReleaseCheck(sIsEngBuild);
- }
-
- /**
- * Halt execution if this isn't the case.
- */
- public static void isTrue(final boolean condition) {
- if (!condition) {
- fail("Expected condition to be true", false);
- }
- }
-
- /**
- * Halt execution if this isn't the case.
- */
- public static void isFalse(final boolean condition) {
- if (condition) {
- fail("Expected condition to be false", false);
- }
- }
-
- /**
- * Halt execution even in release builds if this isn't the case.
- */
- public static void isTrueReleaseCheck(final boolean condition) {
- if (!condition) {
- fail("Expected condition to be true", true);
- }
- }
-
- public static void equals(final int expected, final int actual) {
- if (expected != actual) {
- fail("Expected " + expected + " but got " + actual, false);
- }
- }
-
- public static void equals(final long expected, final long actual) {
- if (expected != actual) {
- fail("Expected " + expected + " but got " + actual, false);
- }
- }
-
- public static void equals(final Object expected, final Object actual) {
- if (expected != actual
- && (expected == null || actual == null || !expected.equals(actual))) {
- fail("Expected " + expected + " but got " + actual, false);
- }
- }
-
- public static void oneOf(final int actual, final int ...expected) {
- for (int value : expected) {
- if (actual == value) {
- return;
- }
- }
- fail("Expected value to be one of " + Arrays.toString(expected) + " but was " + actual);
- }
-
- public static void inRange(
- final int val, final int rangeMinInclusive, final int rangeMaxInclusive) {
- if (val < rangeMinInclusive || val > rangeMaxInclusive) {
- fail("Expected value in range [" + rangeMinInclusive + ", " +
- rangeMaxInclusive + "], but was " + val, false);
- }
- }
-
- public static void inRange(
- final long val, final long rangeMinInclusive, final long rangeMaxInclusive) {
- if (val < rangeMinInclusive || val > rangeMaxInclusive) {
- fail("Expected value in range [" + rangeMinInclusive + ", " +
- rangeMaxInclusive + "], but was " + val, false);
- }
- }
-
- public static void isMainThread() {
- if (Looper.myLooper() != Looper.getMainLooper()
- && !Thread.currentThread().getName().contains(TEST_THREAD_SUBSTRING)) {
- fail("Expected to run on main thread", false);
- }
- }
-
- public static void isNotMainThread() {
- if (Looper.myLooper() == Looper.getMainLooper()
- && !Thread.currentThread().getName().contains(TEST_THREAD_SUBSTRING)) {
- fail("Not expected to run on main thread", false);
- }
- }
-
- /**
- * Halt execution if the value passed in is not null
- * @param obj The object to check
- */
- public static void isNull(final Object obj) {
- if (obj != null) {
- fail("Expected object to be null", false);
- }
- }
-
- /**
- * Halt execution if the value passed in is not null
- * @param obj The object to check
- * @param failureMessage message to print when halting execution
- */
- public static void isNull(final Object obj, final String failureMessage) {
- if (obj != null) {
- fail(failureMessage, false);
- }
- }
-
- /**
- * Halt execution if the value passed in is null
- * @param obj The object to check
- */
- public static void notNull(final Object obj) {
- if (obj == null) {
- fail("Expected value to be non-null", false);
- }
- }
-
- public static void fail(final String message) {
- fail("Assert.fail() called: " + message, false);
- }
-
- private static void fail(final String message, final boolean crashRelease) {
- LogUtil.e(LogUtil.BUGLE_TAG, message);
- if (crashRelease || sShouldCrash) {
- throw new AssertionError(message);
- } else {
- // Find the method whose assertion failed. We're using a depth of 2, because all public
- // Assert methods delegate to this one (see javadoc on getCaller() for details).
- StackTraceElement caller = DebugUtils.getCaller(2);
- if (caller != null) {
- // This log message can be de-obfuscated by the Proguard retrace tool, just like a
- // full stack trace from a crash.
- LogUtil.e(LogUtil.BUGLE_TAG, "\tat " + caller.toString());
- }
- }
- }
-}
diff --git a/src/com/android/messaging/util/AvatarUriUtil.java b/src/com/android/messaging/util/AvatarUriUtil.java
deleted file mode 100644
index df7d085..0000000
--- a/src/com/android/messaging/util/AvatarUriUtil.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.graphics.Color;
-import android.net.Uri;
-import android.net.Uri.Builder;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.data.ParticipantData;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A helper utility for creating {@link android.net.Uri}s to describe what avatar to fetch or
- * generate and will help verify and extract information from avatar {@link android.net.Uri}s.
- *
- * There are three types of avatar {@link android.net.Uri}.
- *
- * 1) Group Avatars - These are avatars which are used to represent a group conversation. Group
- * avatars uris are basically multiple avatar uri which can be any of the below types but not
- * another group avatar. The group avatars can hold anywhere from two to four avatars uri and can
- * be in any of the following format
- * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>
- * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>&p=<avatarUri3>
- * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>&p=<avatarUri3>&p=<avatarUri4>
- *
- * 2) Local Resource - A local resource avatar is use when there is a profile photo for the
- * participant. This can be any local resource.
- *
- * 3) Letter Tile - A letter tile is used when a participant has a name but no profile photo. A
- * letter tile will contain the first code point of the participant's name and a background color
- * based on the hash of the participant's full name. Letter tiles will be in the following format.
- * messaging://avatar/l?n=<fullName>
- *
- * 4) Default Avatars - These are avatars are used when the participant has no profile photo or
- * name. In these cases we use the default person icon with a color background. The color
- * background is based on a hash of the normalized phone number.
- *
- * 5) Default Background Avatars - This is a special case for Default Avatars where we use the
- * default background color for the default avatar.
- *
- * 6) SIM Selector Avatars - These are avatars used in the SIM selector. This may either be a
- * regular local resource avatar (2) or an avatar with a SIM identifier (i.e. SIM background with
- * a letter or a slot number).
- */
-public class AvatarUriUtil {
- private static final int MAX_GROUP_PARTICIPANTS = 4;
-
- public static final String TYPE_GROUP_URI = "g";
- public static final String TYPE_LOCAL_RESOURCE_URI = "r";
- public static final String TYPE_LETTER_TILE_URI = "l";
- public static final String TYPE_DEFAULT_URI = "d";
- public static final String TYPE_DEFAULT_BACKGROUND_URI = "b";
- public static final String TYPE_SIM_SELECTOR_URI = "s";
-
- private static final String SCHEME = "messaging";
- private static final String AUTHORITY = "avatar";
- private static final String PARAM_NAME = "n";
- private static final String PARAM_PRIMARY_URI = "m";
- private static final String PARAM_FALLBACK_URI = "f";
- private static final String PARAM_PARTICIPANT = "p";
- private static final String PARAM_IDENTIFIER = "i";
- private static final String PARAM_SIM_COLOR = "c";
- private static final String PARAM_SIM_SELECTED = "s";
- private static final String PARAM_SIM_INCOMING = "g";
-
- public static final Uri DEFAULT_BACKGROUND_AVATAR = new Uri.Builder().scheme(SCHEME)
- .authority(AUTHORITY).appendPath(TYPE_DEFAULT_BACKGROUND_URI).build();
-
- private static final Uri BLANK_SIM_INDICATOR_INCOMING_URI = createSimIconUri("",
- false /* selected */, Color.TRANSPARENT, true /* incoming */);
- private static final Uri BLANK_SIM_INDICATOR_OUTGOING_URI = createSimIconUri("",
- false /* selected */, Color.TRANSPARENT, false /* incoming */);
-
- /**
- * Creates an avatar uri based on a list of ParticipantData. The list of participants may not
- * be null or empty. Depending on the size of the list either a group avatar uri will be create
- * or an individual's avatar will be created. This will never return a null uri.
- */
- public static Uri createAvatarUri(@NonNull final List<ParticipantData> participants) {
- Assert.notNull(participants);
- Assert.isTrue(!participants.isEmpty());
-
- if (participants.size() == 1) {
- return createAvatarUri(participants.get(0));
- }
-
- final int numParticipants = Math.min(participants.size(), MAX_GROUP_PARTICIPANTS);
- final ArrayList<Uri> avatarUris = new ArrayList<Uri>(numParticipants);
- for (int i = 0; i < numParticipants; i++) {
- avatarUris.add(createAvatarUri(participants.get(i)));
- }
- return AvatarUriUtil.joinAvatarUriToGroup(avatarUris);
- }
-
- /**
- * Joins together a list of valid avatar uri into a group uri.The list of participants may not
- * be null or empty. If a lit of one is given then the first element will be return back
- * instead of a group avatar uri. All uris in the list must be a valid avatar uri. This will
- * never return a null uri.
- */
- public static Uri joinAvatarUriToGroup(@NonNull final List<Uri> avatarUris) {
- Assert.notNull(avatarUris);
- Assert.isTrue(!avatarUris.isEmpty());
-
- if (avatarUris.size() == 1) {
- final Uri firstAvatar = avatarUris.get(0);
- Assert.isTrue(AvatarUriUtil.isAvatarUri(firstAvatar));
- return firstAvatar;
- }
-
- final Builder builder = new Builder();
- builder.scheme(SCHEME);
- builder.authority(AUTHORITY);
- builder.appendPath(TYPE_GROUP_URI);
- final int numParticipants = Math.min(avatarUris.size(), MAX_GROUP_PARTICIPANTS);
- for (int i = 0; i < numParticipants; i++) {
- final Uri uri = avatarUris.get(i);
- Assert.notNull(uri);
- Assert.isTrue(UriUtil.isLocalResourceUri(uri) || AvatarUriUtil.isAvatarUri(uri));
- builder.appendQueryParameter(PARAM_PARTICIPANT, uri.toString());
- }
- return builder.build();
- }
-
- /**
- * Creates an avatar uri based on ParticipantData which may not be null and expected to have
- * profilePhotoUri, fullName and normalizedDestination populated. This will never return a null
- * uri.
- */
- public static Uri createAvatarUri(@NonNull final ParticipantData participant) {
- Assert.notNull(participant);
- final String photoUriString = participant.getProfilePhotoUri();
- final Uri profilePhotoUri = (photoUriString == null) ? null : Uri.parse(photoUriString);
- final String name = participant.getFullName();
- final String destination = participant.getNormalizedDestination();
- final String contactLookupKey = participant.getLookupKey();
- return createAvatarUri(profilePhotoUri, name, destination, contactLookupKey);
- }
-
- /**
- * Creates an avatar uri based on a the input data.
- */
- public static Uri createAvatarUri(final Uri profilePhotoUri, final CharSequence name,
- final String defaultIdentifier, final String contactLookupKey) {
- Uri generatedUri;
- if (!TextUtils.isEmpty(name) && isValidFirstCharacter(name)) {
- generatedUri = AvatarUriUtil.fromName(name, contactLookupKey);
- } else {
- final String identifier = TextUtils.isEmpty(contactLookupKey)
- ? defaultIdentifier : contactLookupKey;
- generatedUri = AvatarUriUtil.fromIdentifier(identifier);
- }
-
- if (profilePhotoUri != null) {
- if (UriUtil.isLocalResourceUri(profilePhotoUri)) {
- return fromLocalResourceWithFallback(profilePhotoUri, generatedUri);
- } else {
- return profilePhotoUri;
- }
- } else {
- return generatedUri;
- }
- }
-
- public static boolean isValidFirstCharacter(final CharSequence name) {
- final char c = name.charAt(0);
- return c != '+';
- }
-
- /**
- * Creates an avatar URI used for the SIM selector.
- * @param participantData the self participant data for an <i>active</i> SIM
- * @param slotIdentifier when null, this will simply use a regular avatar; otherwise, the
- * first letter of slotIdentifier will be used for the icon.
- * @param selected is this the currently selected SIM?
- * @param incoming is this for an incoming message or outgoing message?
- */
- public static Uri createAvatarUri(@NonNull final ParticipantData participantData,
- @Nullable final String slotIdentifier, final boolean selected, final boolean incoming) {
- Assert.notNull(participantData);
- Assert.isTrue(participantData.isActiveSubscription());
- Assert.isTrue(!TextUtils.isEmpty(slotIdentifier) ||
- !TextUtils.isEmpty(participantData.getProfilePhotoUri()));
- if (TextUtils.isEmpty(slotIdentifier)) {
- return createAvatarUri(participantData);
- }
-
- return createSimIconUri(slotIdentifier, selected, participantData.getSubscriptionColor(),
- incoming);
- }
-
- private static Uri createSimIconUri(final String slotIdentifier, final boolean selected,
- final int subColor, final boolean incoming) {
- final Builder builder = new Builder();
- builder.scheme(SCHEME);
- builder.authority(AUTHORITY);
- builder.appendPath(TYPE_SIM_SELECTOR_URI);
- builder.appendQueryParameter(PARAM_IDENTIFIER, slotIdentifier);
- builder.appendQueryParameter(PARAM_SIM_COLOR, String.valueOf(subColor));
- builder.appendQueryParameter(PARAM_SIM_SELECTED, String.valueOf(selected));
- builder.appendQueryParameter(PARAM_SIM_INCOMING, String.valueOf(incoming));
- return builder.build();
- }
-
- public static Uri getBlankSimIndicatorUri(final boolean incoming) {
- return incoming ? BLANK_SIM_INDICATOR_INCOMING_URI : BLANK_SIM_INDICATOR_OUTGOING_URI;
- }
-
- /**
- * Creates an avatar uri from the given local resource Uri, followed by a fallback Uri in case
- * the local resource one could not be loaded.
- */
- private static Uri fromLocalResourceWithFallback(@NonNull final Uri profilePhotoUri,
- @NonNull Uri fallbackUri) {
- Assert.notNull(profilePhotoUri);
- Assert.notNull(fallbackUri);
- final Builder builder = new Builder();
- builder.scheme(SCHEME);
- builder.authority(AUTHORITY);
- builder.appendPath(TYPE_LOCAL_RESOURCE_URI);
- builder.appendQueryParameter(PARAM_PRIMARY_URI, profilePhotoUri.toString());
- builder.appendQueryParameter(PARAM_FALLBACK_URI, fallbackUri.toString());
- return builder.build();
- }
-
- private static Uri fromName(@NonNull final CharSequence name, final String contactLookupKey) {
- Assert.notNull(name);
- final Builder builder = new Builder();
- builder.scheme(SCHEME);
- builder.authority(AUTHORITY);
- builder.appendPath(TYPE_LETTER_TILE_URI);
- final String nameString = String.valueOf(name);
- builder.appendQueryParameter(PARAM_NAME, nameString);
- final String identifier =
- TextUtils.isEmpty(contactLookupKey) ? nameString : contactLookupKey;
- builder.appendQueryParameter(PARAM_IDENTIFIER, identifier);
- return builder.build();
- }
-
- private static Uri fromIdentifier(@NonNull final String identifier) {
- final Builder builder = new Builder();
- builder.scheme(SCHEME);
- builder.authority(AUTHORITY);
- builder.appendPath(TYPE_DEFAULT_URI);
- builder.appendQueryParameter(PARAM_IDENTIFIER, identifier);
- return builder.build();
- }
-
- public static boolean isAvatarUri(@NonNull final Uri uri) {
- Assert.notNull(uri);
- return uri != null && TextUtils.equals(SCHEME, uri.getScheme()) &&
- TextUtils.equals(AUTHORITY, uri.getAuthority());
- }
-
- public static String getAvatarType(@NonNull final Uri uri) {
- Assert.notNull(uri);
- final List<String> path = uri.getPathSegments();
- return path.isEmpty() ? null : path.get(0);
- }
-
- public static String getIdentifier(@NonNull final Uri uri) {
- Assert.notNull(uri);
- return uri.getQueryParameter(PARAM_IDENTIFIER);
- }
-
- public static String getName(@NonNull final Uri uri) {
- Assert.notNull(uri);
- return uri.getQueryParameter(PARAM_NAME);
- }
-
- public static List<String> getGroupParticipantUris(@NonNull final Uri uri) {
- Assert.notNull(uri);
- return uri.getQueryParameters(PARAM_PARTICIPANT);
- }
-
- public static int getSimColor(@NonNull final Uri uri) {
- Assert.notNull(uri);
- return Integer.valueOf(uri.getQueryParameter(PARAM_SIM_COLOR));
- }
-
- public static boolean getSimSelected(@NonNull final Uri uri) {
- Assert.notNull(uri);
- return Boolean.valueOf(uri.getQueryParameter(PARAM_SIM_SELECTED));
- }
-
- public static boolean getSimIncoming(@NonNull final Uri uri) {
- Assert.notNull(uri);
- return Boolean.valueOf(uri.getQueryParameter(PARAM_SIM_INCOMING));
- }
-
- public static Uri getPrimaryUri(@NonNull final Uri uri) {
- Assert.notNull(uri);
- final String primaryUriString = uri.getQueryParameter(PARAM_PRIMARY_URI);
- return primaryUriString == null ? null : Uri.parse(primaryUriString);
- }
-
- public static Uri getFallbackUri(@NonNull final Uri uri) {
- Assert.notNull(uri);
- final String fallbackUriString = uri.getQueryParameter(PARAM_FALLBACK_URI);
- return fallbackUriString == null ? null : Uri.parse(fallbackUriString);
- }
-}
diff --git a/src/com/android/messaging/util/BugleActivityUtil.java b/src/com/android/messaging/util/BugleActivityUtil.java
deleted file mode 100644
index 7f722fd..0000000
--- a/src/com/android/messaging/util/BugleActivityUtil.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.UserManager;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.ui.conversation.ConversationActivity;
-import com.android.messaging.ui.conversationlist.ConversationListActivity;
-
-/**
- * Utility class including logic to verify requirements to run Bugle and other activity startup
- * logic. Called from base Bugle activity classes.
- */
-public class BugleActivityUtil {
-
- private static final int REQUEST_GOOGLE_PLAY_SERVICES = 0;
-
- /**
- * Determine if the requirements for the app to run are met. Log any Activity startup
- * analytics.
- * @param context
- * @param activity is used to launch an error Dialog if necessary
- * @return true if resume should continue normally. Returns false if some requirements to run
- * are not met.
- */
- public static boolean onActivityResume(Context context, Activity activity) {
- DataModel.get().onActivityResume();
- Factory.get().onActivityResume();
-
- // Validate all requirements to run are met
- return checkHasSmsPermissionsForUser(context, activity);
- }
-
- /**
- * Determine if the user doesn't have SMS permissions. This can happen if you are not the phone
- * owner and the owner has disabled your SMS permissions.
- * @param context is the Context used to resolve the user permissions
- * @param activity is the Activity used to launch an error Dialog if necessary
- * @return true if the user has SMS permissions, otherwise false.
- */
- private static boolean checkHasSmsPermissionsForUser(Context context, Activity activity) {
- if (!OsUtil.isAtLeastL()) {
- // UserManager.DISALLOW_SMS added in L. No multiuser phones before this
- return true;
- }
- UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- if (userManager.hasUserRestriction(UserManager.DISALLOW_SMS)) {
- new AlertDialog.Builder(activity)
- .setMessage(R.string.requires_sms_permissions_message)
- .setCancelable(false)
- .setNegativeButton(R.string.requires_sms_permissions_close_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- System.exit(0);
- }
- })
- .show();
- return false;
- }
- return true;
- }
-}
-
diff --git a/src/com/android/messaging/util/BugleApplicationPrefs.java b/src/com/android/messaging/util/BugleApplicationPrefs.java
deleted file mode 100644
index e9fceb4..0000000
--- a/src/com/android/messaging/util/BugleApplicationPrefs.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.Context;
-
-/**
- * Provides interface to access application-wide shared preferences. This includes both the user
- * visible preferences (e.g. the general settings in the settings page), and internal preferences
- * under {@link BuglePrefsKeys}.
- */
-public class BugleApplicationPrefs extends BuglePrefsImpl {
- public BugleApplicationPrefs(Context context) {
- super(context);
- }
-
- @Override
- public String getSharedPreferencesName() {
- return SHARED_PREFERENCES_NAME;
- }
-
- @Override
- protected void validateKey(String key) {
- super.validateKey(key);
- // Callers shouldn't try to access per-subscription preferences from this class
- Assert.isFalse(key.startsWith(SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX));
- }
-
- @Override
- public void onUpgrade(int oldVersion, int newVersion) {
- }
-}
diff --git a/src/com/android/messaging/util/BugleGservices.java b/src/com/android/messaging/util/BugleGservices.java
deleted file mode 100644
index 2e095de..0000000
--- a/src/com/android/messaging/util/BugleGservices.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import com.android.messaging.Factory;
-
-/**
- * A thin wrapper for getting GServices value. During constructor time a one time background thread
- * will cache all GServices key with the prefix of "bugle_". All get calls will wait for Gservices
- * to finish caching the first time. In practice, the background thread will finish before any get
- * request.
- */
-public abstract class BugleGservices {
- static final String BUGLE_GSERVICES_PREFIX = "bugle_";
-
- public static BugleGservices get() {
- return Factory.get().getBugleGservices();
- }
-
- public abstract void registerForChanges(final Runnable r);
-
- /**
- * @param key The key to look up in GServices
- * @param defaultValue The default value if value in GServices is null or if
- * NumberFormatException is caught.
- * @return The corresponding value, or the default value.
- */
- public abstract long getLong(final String key, final long defaultValue);
-
- /**
- * @param key The key to look up in GServices
- * @param defaultValue The default value if value in GServices is null or if
- * NumberFormatException is caught.
- * @return The corresponding value, or the default value.
- */
- public abstract int getInt(final String key, final int defaultValue);
-
- /**
- * @param key The key to look up in GServices
- * @param defaultValue The default value if value in GServices is null.
- * @return The corresponding value, or the default value.
- */
- public abstract boolean getBoolean(final String key, final boolean defaultValue);
-
- /**
- * @param key The key to look up in GServices
- * @param defaultValue The default value if value in GServices is null.
- * @return The corresponding value, or the default value.
- */
- public abstract String getString(final String key, final String defaultValue);
-
- /**
- * @param key The key to look up in GServices
- * @param defaultValue The default value if value in GServices is null.
- * @return The corresponding value, or the default value.
- */
- public abstract float getFloat(final String key, final float defaultValue);
-}
diff --git a/src/com/android/messaging/util/BugleGservicesImpl.java b/src/com/android/messaging/util/BugleGservicesImpl.java
deleted file mode 100644
index 5ef0898..0000000
--- a/src/com/android/messaging/util/BugleGservicesImpl.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-
-/**
- * A thin wrapper for getting GServices value.
- */
-public class BugleGservicesImpl extends BugleGservices {
- public BugleGservicesImpl(final Context context) {
- }
-
- @Override
- public void registerForChanges(final Runnable r) {
- }
-
- /**
- * Asserts that the key has the expected prefix.
- */
- private void assertKeyAndWaitForGservices(final String key) {
- Assert.isTrue(key.startsWith(BUGLE_GSERVICES_PREFIX));
- }
-
- @Override
- public long getLong(final String key, final long defaultValue) {
- assertKeyAndWaitForGservices(key);
- return defaultValue;
- }
-
- @Override
- public int getInt(final String key, final int defaultValue) {
- assertKeyAndWaitForGservices(key);
- return defaultValue;
- }
-
- @Override
- public boolean getBoolean(final String key, final boolean defaultValue) {
- assertKeyAndWaitForGservices(key);
- return defaultValue;
- }
-
- @Override
- public String getString(final String key, final String defaultValue) {
- assertKeyAndWaitForGservices(key);
- return defaultValue;
- }
-
- @Override
- public float getFloat(final String key, final float defaultValue) {
- assertKeyAndWaitForGservices(key);
- return defaultValue;
- }
-}
diff --git a/src/com/android/messaging/util/BugleGservicesKeys.java b/src/com/android/messaging/util/BugleGservicesKeys.java
deleted file mode 100644
index f36dd7f..0000000
--- a/src/com/android/messaging/util/BugleGservicesKeys.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-
-/**
- * List of gservices keys and default values which are in use.
- */
-public final class BugleGservicesKeys {
- private BugleGservicesKeys() {} // do not instantiate
-
- /**
- * Whether to enable extra debugging features on the client. Default is
- * {@value #ENABLE_DEBUGGING_FEATURES_DEFAULT}.
- */
- public static final String ENABLE_DEBUGGING_FEATURES
- = "bugle_debugging";
- public static final boolean ENABLE_DEBUGGING_FEATURES_DEFAULT
- = false;
-
- /**
- * Whether to enable saving extra logs. Default is {@value #ENABLE_LOG_SAVER_DEFAULT}.
- */
- public static final String ENABLE_LOG_SAVER = "bugle_logsaver";
- public static final boolean ENABLE_LOG_SAVER_DEFAULT = false;
-
- /**
- * Time in milliseconds of initial (attempt 1) resend backoff for failing messages
- */
- public static final String INITIAL_MESSAGE_RESEND_DELAY_MS = "bugle_resend_delay_in_millis";
- public static final long INITIAL_MESSAGE_RESEND_DELAY_MS_DEFAULT = 5 * 1000L;
-
- /**
- * Time in milliseconds of max resend backoff for failing messages
- */
- public static final String MAX_MESSAGE_RESEND_DELAY_MS = "bugle_max_resend_delay_in_millis";
- public static final long MAX_MESSAGE_RESEND_DELAY_MS_DEFAULT = 2 * 60 * 60 * 1000L;
-
- /**
- * Time in milliseconds of resend window for unsent messages
- */
- public static final String MESSAGE_RESEND_TIMEOUT_MS = "bugle_resend_timeout_in_millis";
- public static final long MESSAGE_RESEND_TIMEOUT_MS_DEFAULT = 20 * 60 * 1000L;
-
- /**
- * Time in milliseconds of download window for new mms notifications
- */
- public static final String MESSAGE_DOWNLOAD_TIMEOUT_MS = "bugle_download_timeout_in_millis";
- public static final long MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT = 20 * 60 * 1000L;
-
- /**
- * Time in milliseconds for SMS send timeout
- */
- public static final String SMS_SEND_TIMEOUT_IN_MILLIS = "bugle_sms_send_timeout";
- public static final long SMS_SEND_TIMEOUT_IN_MILLIS_DEFAULT = 5 * 60 * 1000L;
-
- /**
- * Keys to control the SMS sync batch size. The batch size is defined by the number
- * of messages that incur local database change, e.g. importing messages and
- * deleting messages.
- *
- * 1. The minimum size for a batch and
- * 2. The maximum size for a batch.
- * The first batch uses the minimum size for probing. Set this to a small number for the
- * first sync batch to make sure the user sees SMS showing up in conversations quickly
- * Use these two settings to limit the number of messages to sync in each batch.
- * The minimum is to make sure we always make progress during sync. The maximum is
- * to limit the sync batch size within a reasonable range (needs to fit in an intent).
- * 3. The time limit controls the limit of time duration of a sync batch. We can
- * not control this directly due to the batching nature of sync. So this provides
- * heuristics. We may sometime exceeds the limit if our calculation is off due to
- * whatever reasons. Keeping this low ensures responsiveness of the application.
- * 4. The limit on number of total messages to scan in one batch.
- */
- public static final String SMS_SYNC_BATCH_SIZE_MIN =
- "bugle_sms_sync_batch_size_min";
- public static final int SMS_SYNC_BATCH_SIZE_MIN_DEFAULT = 80;
- public static final String SMS_SYNC_BATCH_SIZE_MAX =
- "bugle_sms_sync_batch_size_max";
- public static final int SMS_SYNC_BATCH_SIZE_MAX_DEFAULT = 1000;
- public static final String SMS_SYNC_BATCH_TIME_LIMIT_MILLIS =
- "bugle_sms_sync_batch_time_limit";
- public static final long SMS_SYNC_BATCH_TIME_LIMIT_MILLIS_DEFAULT = 400;
- public static final String SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN =
- "bugle_sms_sync_batch_max_messages_to_scan";
- public static final int SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN_DEFAULT =
- SMS_SYNC_BATCH_SIZE_MAX_DEFAULT * 4;
-
- /**
- * Time in ms for sync to backoff from "now" to the latest message that will be sync'd.
- *
- * This controls the best case for how out of date the application will appear to be
- * when bringing in changes made outside the application. It also represents a buffer
- * to ensure that sync doesn't trigger based on changes made within the application.
- */
- public static final String SMS_SYNC_BACKOFF_TIME_MILLIS =
- "bugle_sms_sync_backoff_time";
- public static final long SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT = 5000L;
-
- /**
- * Just in case if we fall into a loop of full sync -> still not synchronized -> full sync ...
- * This forces a backoff time so that we at most do full sync once a while (an hour by default)
- */
- public static final String SMS_FULL_SYNC_BACKOFF_TIME_MILLIS =
- "bugle_sms_full_sync_backoff_time";
- public static final long SMS_FULL_SYNC_BACKOFF_TIME_MILLIS_DEFAULT = 60 * 60 * 1000;
-
- /**
- * Time duration to retain the most recent SMS messages for SMS storage purging
- *
- * Format:
- * <number>(w|m|y)
- * Examples:
- * "1y" -- a year
- * "2w" -- two weeks
- * "6m" -- six months
- */
- public static final String SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION =
- "bugle_sms_storage_purging_message_retaining_duration";
- public static final String SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION_DEFAULT = "1m";
-
- /**
- * MMS UA profile url.
- *
- * This is used on all Android devices running Hangout, so cannot just host the profile of the
- * latest and greatest phones. However, if we're on KitKat or below we can't get the phone's
- * UA profile and thus we need to send them the default url.
- */
- public static final String MMS_UA_PROFILE_URL =
- "bugle_mms_uaprofurl";
- public static final String MMS_UA_PROFILE_URL_DEFAULT =
- "http://www.gstatic.com/android/sms/mms_ua_profile.xml";
-
- /**
- * MMS apn mmsc
- */
- public static final String MMS_MMSC =
- "bugle_mms_mmsc";
-
- /**
- * MMS apn proxy ip address
- */
- public static final String MMS_PROXY_ADDRESS =
- "bugle_mms_proxy_address";
-
- /**
- * MMS apn proxy port
- */
- public static final String MMS_PROXY_PORT =
- "bugle_mms_proxy_port";
-
- /**
- * List of known SMS system messages that we will ignore (no deliver, no abort) so that the
- * user doesn't see them and the appropriate app is able to handle them. We are delivering
- * these as a \n delimited list of patterns, however we should eventually move to storing
- * them with the per-carrier mms config xml file.
- */
- public static final String SMS_IGNORE_MESSAGE_REGEX =
- "bugle_sms_ignore_message_regex";
- public static final String SMS_IGNORE_MESSAGE_REGEX_DEFAULT = "";
-
- /**
- * When receiving or importing an mms, limit the length of text to this limit. Huge blocks
- * of text can cause the app to hang/ANR/or crash in native text code..
- */
- public static final String MMS_TEXT_LIMIT = "bugle_mms_text_limit";
- public static final int MMS_TEXT_LIMIT_DEFAULT = 2000;
-
- /**
- * Max number of attachments the user may add to a single message.
- */
- public static final String MMS_ATTACHMENT_LIMIT = "bugle_mms_attachment_limit";
- public static final int MMS_ATTACHMENT_LIMIT_DEFAULT = 10;
-
- /**
- * The max number of messages to show in a single conversation notification. We always show
- * the most recent message. If this value is >1, we may also include prior messages as well.
- */
- public static final String MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION =
- "bugle_max_messages_in_conversation_notification";
- public static final int MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_DEFAULT = 7;
-
- /**
- * Time (in seconds) between notification ringing for incoming messages of the same
- * conversation. We won't ding more often than this value for messages coming in at a high rate.
- */
- public static final String NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS
- = "bugle_notification_time_between_rings_seconds";
- public static final int NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS_DEFAULT = 10;
-
- /**
- * The max number of messages to show in a single conversation notification, when a wearable
- * device (i.e. smartwatch) is paired with the phone. Watches have a different UX model and
- * less screen real estate, so we may want to optimize for that case. Note that if a wearable
- * is paired, this value will apply to notifications as shown both on the watch and the phone.
- */
- public static final String MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE =
- "bugle_max_messages_in_conversation_notification_with_wearable";
- public static final int MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE_DEFAULT = 1;
-
- /**
- * Regular expression to match against query. If it matches then display
- * the query plan for this query.
- */
- public static final String EXPLAIN_QUERY_PLAN_REGEXP = "bugle_query_plan_regexp";
-
- /**
- * Whether asserts are fatal on user/userdebug builds.
- * Default is {@value #ASSERTS_FATAL_DEFAULT}.
- */
- public static final String ASSERTS_FATAL = "bugle_asserts_fatal";
- public static final boolean ASSERTS_FATAL_DEFAULT = false;
-
- /**
- * Whether to use API for sending/downloading MMS (if present, true for L).
- * Default is {@value #USE_MMS_API_IF_PRESENT_DEFAULT}.
- */
- public static final String USE_MMS_API_IF_PRESENT = "bugle_use_mms_api";
- public static final boolean USE_MMS_API_IF_PRESENT_DEFAULT = true;
-
- /**
- * Whether to always auto-complete email addresses for sending MMS. By default, Bugle starts
- * to auto-complete after the user has typed the "@" character.
- * Default is (@value ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT}.
- */
- public static final String ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS =
- "bugle_always_autocomplete_email_address";
- public static final boolean ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT = false;
-
- // We typically request an aspect ratio close the the screen size, but some cameras can be
- // flaky and not work well in certain aspect ratios. This allows us to guide the CameraManager
- // to pick a more reliable aspect ratio. The value is a float like 1.333f or 1.777f. There is
- // no hard coded default because the default is the screen aspect ratio.
- public static final String CAMERA_ASPECT_RATIO = "bugle_camera_aspect_ratio";
-
- /**
- * The recent time range within which we should check MMS WAP Push duplication
- * If the value is 0, it signals that we should use old dedup algorithm for wap push
- */
- public static final String MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS =
- "bugle_mms_wap_push_dedup_time_limit_secs";
- public static final long MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS_DEFAULT = 7 * 24 * 3600; // 7 days
-
- /**
- * Whether to use persistent, on-disk LogSaver
- */
- public static final String PERSISTENT_LOGSAVER = "bugle_persistent_logsaver";
- public static final boolean PERSISTENT_LOGSAVER_DEFAULT = false;
-
- /**
- * For in-memory LogSaver, what's the size of memory buffer in number of records
- */
- public static final String IN_MEMORY_LOGSAVER_RECORD_COUNT =
- "bugle_in_memory_logsaver_record_count";
- public static final int IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT = 500;
-
- /**
- * For on-disk LogSaver, what's the size of file rotation set
- */
- public static final String PERSISTENT_LOGSAVER_ROTATION_SET_SIZE =
- "bugle_persistent_logsaver_rotation_set_size";
- public static final int PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT = 8;
-
- /**
- * For on-disk LogSaver, what's the byte limit of a single log file
- */
- public static final String PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES =
- "bugle_persistent_logsaver_file_limit";
- public static final int PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT = 256 * 1024; // 256KB
-
- /**
- * We concatenate all text parts in an MMS to form the message text. This specifies
- * the separator between the combinated text parts. Default is ' ' (space).
- */
- public static final String MMS_TEXT_CONCAT_SEPARATOR = "bugle_mms_text_concat_separator";
- public static final String MMS_TEXT_CONCAT_SEPARATOR_DEFAULT = " ";
-
- /**
- * Whether to enable transcoding GIFs. We sometimes need to compress GIFs to make them small
- * enough to send via MMS (which often limits messages to 1 MB in size).
- */
- public static final String ENABLE_GIF_TRANSCODING = "bugle_gif_transcoding";
- public static final boolean ENABLE_GIF_TRANSCODING_DEFAULT = true;
-}
diff --git a/src/com/android/messaging/util/BuglePrefs.java b/src/com/android/messaging/util/BuglePrefs.java
deleted file mode 100644
index 74a0d46..0000000
--- a/src/com/android/messaging/util/BuglePrefs.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import com.android.messaging.Factory;
-
-/**
- * Thin wrapper to get/set shared prefs values.
- */
-public abstract class BuglePrefs {
- /**
- * Shared preferences name for preferences applicable to the entire app.
- */
- public static final String SHARED_PREFERENCES_NAME = "bugle";
-
- /**
- * Shared preferences name for subscription-specific preferences.
- * Note: for all subscription-specific preferences, please prefix the shared preference keys
- * with "buglesub_", so that Bugle may perform runtime validations on preferences to make sure
- * you don't accidentally write per-subscription settings into the general pref file, and vice
- * versa.
- */
- public static final String SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX = "buglesub_";
-
- /**
- * A placeholder base version for Bugle builds where no shared pref version was defined.
- */
- public static final int NO_SHARED_PREFERENCES_VERSION = -1;
-
- /**
- * Returns the shared preferences file name to use.
- * Subclasses should override and return the shared preferences file.
- */
- public abstract String getSharedPreferencesName();
-
-
- /**
- * Handles pref version upgrade.
- */
- public abstract void onUpgrade(final int oldVersion, final int newVersion);
-
- /**
- * Gets the SharedPreferences accessor to the application-wide preferences.
- */
- public static BuglePrefs getApplicationPrefs() {
- return Factory.get().getApplicationPrefs();
- }
-
- /**
- * Gets the SharedPreferences accessor to the subscription-specific preferences.
- */
- public static BuglePrefs getSubscriptionPrefs(final int subId) {
- return Factory.get().getSubscriptionPrefs(subId);
- }
-
- /**
- * @param key The key to look up in shared prefs
- * @param defaultValue The default value if value in shared prefs is null or if
- * NumberFormatException is caught.
- * @return The corresponding value, or the default value.
- */
- public abstract int getInt(final String key, final int defaultValue);
-
- /**
- * @param key The key to look up in shared prefs
- * @param defaultValue The default value if value in shared prefs is null or if
- * NumberFormatException is caught.
- * @return The corresponding value, or the default value.
- */
- public abstract long getLong(final String key, final long defaultValue);
-
- /**
- * @param key The key to look up in shared prefs
- * @param defaultValue The default value if value in shared prefs is null.
- * @return The corresponding value, or the default value.
- */
- public abstract boolean getBoolean(final String key, final boolean defaultValue);
-
- /**
- * @param key The key to look up in shared prefs
- * @param defaultValue The default value if value in shared prefs is null.
- * @return The corresponding value, or the default value.
- */
- public abstract String getString(final String key, final String defaultValue);
-
- /**
- * @param key The key to look up in shared prefs
- * @return The corresponding value, or null if not found.
- */
- public abstract byte[] getBytes(final String key);
-
- /**
- * @param key The key to set in shared prefs
- * @param value The value to assign to the key
- */
- public abstract void putInt(final String key, final int value);
-
- /**
- * @param key The key to set in shared prefs
- * @param value The value to assign to the key
- */
- public abstract void putLong(final String key, final long value);
-
- /**
- * @param key The key to set in shared prefs
- * @param value The value to assign to the key
- */
- public abstract void putBoolean(final String key, final boolean value);
-
- /**
- * @param key The key to set in shared prefs
- * @param value The value to assign to the key
- */
- public abstract void putString(final String key, final String value);
-
- /**
- * @param key The key to set in shared prefs
- * @param value The value to assign to the key
- */
- public abstract void putBytes(final String key, final byte[] value);
-
- /**
- * @param key The key to remove from shared prefs
- */
- public abstract void remove(String key);
-}
diff --git a/src/com/android/messaging/util/BuglePrefsImpl.java b/src/com/android/messaging/util/BuglePrefsImpl.java
deleted file mode 100644
index 7563040..0000000
--- a/src/com/android/messaging/util/BuglePrefsImpl.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Base64;
-
-/**
- * Thin wrapper to get/set shared prefs values.
- */
-public abstract class BuglePrefsImpl extends BuglePrefs {
-
- private final Context mContext;
-
- public BuglePrefsImpl(final Context context) {
- mContext = context;
- }
-
- /**
- * Validate the prefs key passed in. Subclasses should override this as needed to perform
- * runtime checks (such as making sure per-subscription settings don't sneak into application-
- * wide settings).
- */
- protected void validateKey(String key) {
- }
-
- @Override
- public int getInt(final String key, final int defaultValue) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- return prefs.getInt(key, defaultValue);
- }
-
- @Override
- public long getLong(final String key, final long defaultValue) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- return prefs.getLong(key, defaultValue);
- }
-
- @Override
- public boolean getBoolean(final String key, final boolean defaultValue) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- return prefs.getBoolean(key, defaultValue);
- }
-
- @Override
- public String getString(final String key, final String defaultValue) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- return prefs.getString(key, defaultValue);
- }
-
- @Override
- public byte[] getBytes(String key) {
- final String byteValue = getString(key, null);
- return byteValue == null ? null : Base64.decode(byteValue, Base64.DEFAULT);
- }
-
- @Override
- public void putInt(final String key, final int value) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- final SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(key, value);
- editor.apply();
- }
-
- @Override
- public void putLong(final String key, final long value) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- final SharedPreferences.Editor editor = prefs.edit();
- editor.putLong(key, value);
- editor.apply();
- }
-
- @Override
- public void putBoolean(final String key, final boolean value) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- final SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(key, value);
- editor.apply();
- }
-
- @Override
- public void putString(final String key, final String value) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- final SharedPreferences.Editor editor = prefs.edit();
- editor.putString(key, value);
- editor.apply();
- }
-
- @Override
- public void putBytes(String key, byte[] value) {
- final String encodedBytes = Base64.encodeToString(value, Base64.DEFAULT);
- putString(key, encodedBytes);
- }
-
- @Override
- public void remove(final String key) {
- validateKey(key);
- final SharedPreferences prefs = mContext.getSharedPreferences(
- getSharedPreferencesName(), Context.MODE_PRIVATE);
- final SharedPreferences.Editor editor = prefs.edit();
- editor.remove(key);
- editor.apply();
- }
-}
diff --git a/src/com/android/messaging/util/BuglePrefsKeys.java b/src/com/android/messaging/util/BuglePrefsKeys.java
deleted file mode 100644
index ae409bc..0000000
--- a/src/com/android/messaging/util/BuglePrefsKeys.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-/**
- * List of shared preferences keys and default values. These are all internal
- * (not user-visible) preferences. Preferences that are exposed via the Settings
- * activity should be defined in the constants.xml resource file instead.
- */
-public final class BuglePrefsKeys {
- private BuglePrefsKeys() {} // do not instantiate
-
- /**
- * Bugle's shared preferences version
- */
- public static final String SHARED_PREFERENCES_VERSION =
- "shared_preferences_version";
- public static final int SHARED_PREFERENCES_VERSION_DEFAULT =
- BuglePrefs.NO_SHARED_PREFERENCES_VERSION;
-
- /**
- * Last time that we ran a a sync (in millis)
- */
- public static final String LAST_SYNC_TIME
- = "last_sync_time_millis";
- public static final long LAST_SYNC_TIME_DEFAULT
- = -1;
-
- /**
- * Last time that we ran a full sync (in millis)
- */
- public static final String LAST_FULL_SYNC_TIME
- = "last_full_sync_time_millis";
- public static final long LAST_FULL_SYNC_TIME_DEFAULT
- = -1;
-
- /**
- * Timestamp of the message for which we last did a message notification.
- */
- public static final String LATEST_NOTIFICATION_MESSAGE_TIMESTAMP
- = "latest_notification_message_timestamp";
-
- /**
- * The last selected chooser index in the media picker.
- */
- public static final String SELECTED_MEDIA_PICKER_CHOOSER_INDEX
- = "selected_media_picker_chooser_index";
- public static final int SELECTED_MEDIA_PICKER_CHOOSER_INDEX_DEFAULT
- = -1;
-
- /**
- * The attempt number when retrying ProcessPendingMessagesAction
- */
- public static final String PROCESS_PENDING_MESSAGES_RETRY_COUNT
- = "process_pending_retry";
-
-}
diff --git a/src/com/android/messaging/util/BugleSubscriptionPrefs.java b/src/com/android/messaging/util/BugleSubscriptionPrefs.java
deleted file mode 100644
index 039712a..0000000
--- a/src/com/android/messaging/util/BugleSubscriptionPrefs.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-
-/**
- * Provides interface to access per-subscription shared preferences. We have one instance of
- * this per active subscription.
- */
-public class BugleSubscriptionPrefs extends BuglePrefsImpl {
- private final int mSubId;
-
- public BugleSubscriptionPrefs(final Context context, final int subId) {
- super(context);
- mSubId = subId;
- }
-
- @Override
- public String getSharedPreferencesName() {
- return SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX + String.valueOf(mSubId);
- }
-
- @Override
- protected void validateKey(String key) {
- super.validateKey(key);
- // Callers should only access per-subscription preferences from this class
- Assert.isTrue(key.startsWith(SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX));
- }
-
- @Override
- public void onUpgrade(final int oldVersion, final int newVersion) {
- switch (oldVersion) {
- case BuglePrefs.NO_SHARED_PREFERENCES_VERSION:
- // Upgrade to version 1. Adding per-subscription shared prefs.
- // Migrate values from the application-wide settings.
- migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "delivery_reports",
- R.string.delivery_reports_pref_key, R.bool.delivery_reports_pref_default);
- migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "auto_retrieve_mms",
- R.string.auto_retrieve_mms_pref_key, R.bool.auto_retrieve_mms_pref_default);
- migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(),
- "auto_retrieve_mms_when_roaming",
- R.string.auto_retrieve_mms_when_roaming_pref_key,
- R.bool.auto_retrieve_mms_when_roaming_pref_default);
- migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "group_messaging",
- R.string.group_mms_pref_key, R.bool.group_mms_pref_default);
-
- if (PhoneUtils.getDefault().getActiveSubscriptionCount() == 1) {
- migratePrefStringInternal(BuglePrefs.getApplicationPrefs(), "mms_phone_number",
- R.string.mms_phone_number_pref_key, null);
- }
- }
- }
-
- private void migratePrefBooleanInternal(final BuglePrefs oldPrefs, final String oldKey,
- final int newKeyResId, final int defaultValueResId) {
- final Resources resources = Factory.get().getApplicationContext().getResources();
- final boolean defaultValue = resources.getBoolean(defaultValueResId);
- final boolean oldValue = oldPrefs.getBoolean(oldKey, defaultValue);
-
- // Only migrate pref value if it's different than the default.
- if (oldValue != defaultValue) {
- putBoolean(resources.getString(newKeyResId), oldValue);
- }
- }
-
- private void migratePrefStringInternal(final BuglePrefs oldPrefs, final String oldKey,
- final int newKeyResId, final String defaultValue) {
- final Resources resources = Factory.get().getApplicationContext().getResources();
- final String oldValue = oldPrefs.getString(oldKey, defaultValue);
-
- // Only migrate pref value if it's different than the default.
- if (!TextUtils.equals(oldValue, defaultValue)) {
- putString(resources.getString(newKeyResId), oldValue);
- }
- }
-}
diff --git a/src/com/android/messaging/util/BugleWidgetPrefs.java b/src/com/android/messaging/util/BugleWidgetPrefs.java
deleted file mode 100644
index 63ba567..0000000
--- a/src/com/android/messaging/util/BugleWidgetPrefs.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.Context;
-
-/**
- * Provides interface to access shared preferences used by bugle widgets.
- */
-public class BugleWidgetPrefs extends BuglePrefsImpl {
- /**
- * Shared preferences name for preferences applicable to the entire app.
- */
- public static final String SHARED_PREFERENCES_WIDGET_NAME = "bugle_widgets";
-
- public BugleWidgetPrefs(Context context) {
- super(context);
- }
-
- @Override
- public String getSharedPreferencesName() {
- return SHARED_PREFERENCES_WIDGET_NAME;
- }
-
- @Override
- public void onUpgrade(int oldVersion, int newVersion) {
- }
-}
diff --git a/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java b/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java
deleted file mode 100644
index 6cf2f25..0000000
--- a/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.SnackBar;
-import com.android.messaging.ui.UIIntents;
-
-public class ChangeDefaultSmsAppHelper {
- private Runnable mRunAfterMadeDefault;
- private ChangeSmsAppSettingRunnable mChangeSmsAppSettingRunnable;
-
- private static final int REQUEST_SET_DEFAULT_SMS_APP = 1;
-
- /**
- * When there's some condition that prevents an operation, such as sending a message,
- * call warnOfMissingActionConditions to put up a toast and allow the user to repair
- * that condition.
- * @param sending - true if we're called during a sending operation
- * @param runAfterMadeDefault - a runnable to run after the user responds
- * positively to the condition prompt and resolves the condition. It is
- * preferable to specify the value in {@link #handleChangeDefaultSmsResult}
- * as that handles the case where the process gets restarted.
- * If null, the user will be shown a generic toast message.
- * @param composeView - compose view that may have the keyboard opened and focused
- * @param rootView - if non-null, use this to attach a snackBar
- * @param activity - calling activity
- * @param fragment - calling fragment, may be null if called directly from an activity
- */
- public void warnOfMissingActionConditions(final boolean sending,
- final Runnable runAfterMadeDefault,
- final View composeView, final View rootView,
- final Activity activity, final Fragment fragment) {
- final PhoneUtils phoneUtils = PhoneUtils.getDefault();
- final boolean isSmsCapable = phoneUtils.isSmsCapable();
- final boolean hasPreferredSmsSim = phoneUtils.getHasPreferredSmsSim();
- final boolean isDefaultSmsApp = phoneUtils.isDefaultSmsApp();
-
- // Supports SMS?
- if (!isSmsCapable) {
- UiUtils.showToast(R.string.sms_disabled);
-
- // Has a preferred sim?
- } else if (!hasPreferredSmsSim) {
- UiUtils.showToast(R.string.no_preferred_sim_selected);
-
- // Is the default sms app?
- } else if (!isDefaultSmsApp) {
- mChangeSmsAppSettingRunnable = new ChangeSmsAppSettingRunnable(activity, fragment);
- promptToChangeDefaultSmsApp(sending, runAfterMadeDefault,
- composeView, rootView, activity);
- }
-
- LogUtil.w(LogUtil.BUGLE_TAG, "Unsatisfied action condition: "
- + "isSmsCapable=" + isSmsCapable + ", "
- + "hasPreferredSmsSim=" + hasPreferredSmsSim + ", "
- + "isDefaultSmsApp=" + isDefaultSmsApp);
- }
-
- private void promptToChangeDefaultSmsApp(final boolean sending,
- final Runnable runAfterMadeDefault,
- final View composeView, final View rootView,
- final Activity activity) {
- if (composeView != null) {
- // Avoid bug in system which puts soft keyboard over dialog after orientation change
- ImeUtil.hideSoftInput(activity, composeView);
- }
- mRunAfterMadeDefault = runAfterMadeDefault;
-
- if (rootView == null) {
- // Immediately open the system "Change default SMS app?" dialog setting.
- mChangeSmsAppSettingRunnable.run();
- } else {
- UiUtils.showSnackBarWithCustomAction(activity,
- rootView,
- activity.getString(sending ? R.string.requires_default_sms_app_to_send :
- R.string.requires_default_sms_app),
- SnackBar.Action.createCustomAction(mChangeSmsAppSettingRunnable,
- activity.getString(R.string.requires_default_sms_change_button)),
- null /* interactions */,
- SnackBar.Placement.above(composeView));
- }
- }
-
- private class ChangeSmsAppSettingRunnable implements Runnable {
- private final Activity mActivity;
- private final Fragment mFragment;
-
- public ChangeSmsAppSettingRunnable(final Activity activity, final Fragment fragment) {
- mActivity = activity;
- mFragment = fragment;
- }
-
- @Override
- public void run() {
- try {
- final Intent intent = UIIntents.get().getChangeDefaultSmsAppIntent(mActivity);
- if (mFragment != null) {
- mFragment.startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
- } else {
- mActivity.startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
- }
- } catch (final ActivityNotFoundException ex) {
- // We shouldn't get here, but the monkey on JB MR0 can trigger it.
- LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex);
- UiUtils.showToastAtBottom(R.string.activity_not_found_message);
- }
- }
- }
-
- public void handleChangeDefaultSmsResult(
- final int requestCode,
- final int resultCode,
- Runnable runAfterMadeDefault) {
- Assert.isTrue(mRunAfterMadeDefault == null || runAfterMadeDefault == null);
- if (runAfterMadeDefault == null) {
- runAfterMadeDefault = mRunAfterMadeDefault;
- }
-
- if (requestCode == REQUEST_SET_DEFAULT_SMS_APP) {
- if (resultCode == Activity.RESULT_OK) {
- // mRunAfterMadeDefault can be null if it was set only in
- // promptToChangeDefaultSmsApp, and the process subsequently restarted when the
- // user momentarily switched to another app. In that case, we'll simply show a
- // generic toast since we do not know what the runnable was supposed to do.
- if (runAfterMadeDefault != null) {
- runAfterMadeDefault.run();
- } else {
- UiUtils.showToast(R.string.toast_after_setting_default_sms_app);
- }
- }
- mRunAfterMadeDefault = null; // don't want to accidentally run it again
- }
- }
-}
-
-
diff --git a/src/com/android/messaging/util/CircularArray.java b/src/com/android/messaging/util/CircularArray.java
deleted file mode 100644
index db6cf12..0000000
--- a/src/com/android/messaging/util/CircularArray.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-/**
- * Very simple circular array implementation.
- *
- * @param <E> The element type of this list.
- * @LibraryInternal
- */
-public class CircularArray<E> {
- private int mNextWriter;
- private boolean mHasWrapped;
- private int mMaxCount;
- Object mList[];
-
- /**
- * Constructor for CircularArray.
- *
- * @param count Max elements to hold in the list.
- */
- public CircularArray(int count) {
- mMaxCount = count;
- clear();
- }
-
- /**
- * Reset the list.
- */
- public void clear() {
- mNextWriter = 0;
- mHasWrapped = false;
- mList = new Object[mMaxCount];
- }
-
- /**
- * Add an element to the end of the list.
- *
- * @param object The object to add.
- */
- public void add(E object) {
- mList[mNextWriter] = object;
- ++mNextWriter;
- if (mNextWriter == mMaxCount) {
- mNextWriter = 0;
- mHasWrapped = true;
- }
- }
-
- /**
- * Get the number of elements in the list. This will be 0 <= returned count <= max count
- *
- * @return Elements in the circular list.
- */
- public int count() {
- if (mHasWrapped) {
- return mMaxCount;
- } else {
- return mNextWriter;
- }
- }
-
- /**
- * Return null if the list hasn't wrapped yet. Otherwise return the next object that would be
- * overwritten. Can be useful to avoid extra allocations.
- *
- * @return
- */
- @SuppressWarnings("unchecked")
- public E getFree() {
- if (!mHasWrapped) {
- return null;
- } else {
- return (E) mList[mNextWriter];
- }
- }
-
- /**
- * Get the object at index. Index 0 is the oldest item inserted into the list. Index (count() -
- * 1) is the newest.
- *
- * @param index Index to retrieve.
- * @return Object at index.
- */
- @SuppressWarnings("unchecked")
- public E get(int index) {
- if (mHasWrapped) {
- int wrappedIndex = index + mNextWriter;
- if (wrappedIndex >= mMaxCount) {
- wrappedIndex -= mMaxCount;
- }
- return (E) mList[wrappedIndex];
- } else {
- return (E) mList[index];
- }
- }
-}
diff --git a/src/com/android/messaging/util/ConnectivityUtil.java b/src/com/android/messaging/util/ConnectivityUtil.java
deleted file mode 100644
index 49f6e0a..0000000
--- a/src/com/android/messaging/util/ConnectivityUtil.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
-import android.telephony.TelephonyManager;
-
-public class ConnectivityUtil {
- // Assume not connected until informed differently
- private volatile int mCurrentServiceState = ServiceState.STATE_POWER_OFF;
-
- private final TelephonyManager mTelephonyManager;
- private final Context mContext;
- private final ConnectivityBroadcastReceiver mReceiver;
- private final ConnectivityManager mConnMgr;
-
- private ConnectivityListener mListener;
- private final IntentFilter mIntentFilter;
-
- public interface ConnectivityListener {
- public void onConnectivityStateChanged(final Context context, final Intent intent);
- public void onPhoneStateChanged(final Context context, int serviceState);
- }
-
- public ConnectivityUtil(final Context context) {
- mContext = context;
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- mConnMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- mReceiver = new ConnectivityBroadcastReceiver();
- mIntentFilter = new IntentFilter();
- mIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- }
-
- public int getCurrentServiceState() {
- return mCurrentServiceState;
- }
-
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onServiceStateChanged(final ServiceState serviceState) {
- if (mCurrentServiceState != serviceState.getState()) {
- mCurrentServiceState = serviceState.getState();
- onPhoneStateChanged(mCurrentServiceState);
- }
- }
-
- @Override
- public void onDataConnectionStateChanged(final int state) {
- mCurrentServiceState = (state == TelephonyManager.DATA_DISCONNECTED) ?
- ServiceState.STATE_OUT_OF_SERVICE : ServiceState.STATE_IN_SERVICE;
- }
- };
-
- private void onPhoneStateChanged(final int serviceState) {
- final ConnectivityListener listener = mListener;
- if (listener != null) {
- listener.onPhoneStateChanged(mContext, serviceState);
- }
- }
-
- private void onConnectivityChanged(final Context context, final Intent intent) {
- final ConnectivityListener listener = mListener;
- if (listener != null) {
- listener.onConnectivityStateChanged(context, intent);
- }
- }
-
- public void register(final ConnectivityListener listener) {
- Assert.isTrue(mListener == null || mListener == listener);
- if (mListener == null) {
- if (mTelephonyManager != null) {
- mCurrentServiceState = (PhoneUtils.getDefault().isAirplaneModeOn() ?
- ServiceState.STATE_POWER_OFF : ServiceState.STATE_IN_SERVICE);
- mTelephonyManager.listen(mPhoneStateListener,
- PhoneStateListener.LISTEN_SERVICE_STATE);
- }
- if (mConnMgr != null) {
- mContext.registerReceiver(mReceiver, mIntentFilter);
- }
- }
- mListener = listener;
- }
-
- public void unregister() {
- if (mListener != null) {
- if (mTelephonyManager != null) {
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- mCurrentServiceState = ServiceState.STATE_POWER_OFF;
- }
- if (mConnMgr != null) {
- mContext.unregisterReceiver(mReceiver);
- }
- }
- mListener = null;
- }
-
- /**
- * Connectivity change broadcast receiver. This gets the network connectivity updates.
- * In case we don't get the active connectivity when we first acquire the network,
- * this receiver will notify us when it is connected, so to unblock the waiting thread
- * which is sending the message.
- */
- public class ConnectivityBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (!intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
- return;
- }
-
- onConnectivityChanged(context, intent);
- }
- }
-
- private int mSignalLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-
- // We use a separate instance than mPhoneStateListener because the lifetimes are different.
- private final PhoneStateListener mSignalStrengthListener = new PhoneStateListener() {
- @Override
- public void onSignalStrengthsChanged(final SignalStrength signalStrength) {
- mSignalLevel = getLevel(signalStrength);
- }
- };
-
- public void registerForSignalStrength() {
- mTelephonyManager.listen(
- mSignalStrengthListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
- }
-
- public void unregisterForSignalStrength() {
- mTelephonyManager.listen(mSignalStrengthListener, PhoneStateListener.LISTEN_NONE);
- }
-
- /**
- * @param subId This is ignored because TelephonyManager does not support it.
- * @return Signal strength as level 0..4
- */
- public int getSignalLevel(final int subId) {
- return mSignalLevel;
- }
-
- private static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
- private static final int SIGNAL_STRENGTH_POOR = 1;
- private static final int SIGNAL_STRENGTH_MODERATE = 2;
- private static final int SIGNAL_STRENGTH_GOOD = 3;
- private static final int SIGNAL_STRENGTH_GREAT = 4;
-
- private static final int GSM_SIGNAL_STRENGTH_GREAT = 12;
- private static final int GSM_SIGNAL_STRENGTH_GOOD = 8;
- private static final int GSM_SIGNAL_STRENGTH_MODERATE = 8;
-
- private static int getLevel(final SignalStrength signalStrength) {
- if (signalStrength.isGsm()) {
- // From frameworks/base/telephony/java/android/telephony/CellSignalStrengthGsm.java
-
- // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
- // asu = 0 (-113dB or less) is very weak
- // signal, its better to show 0 bars to the user in such cases.
- // asu = 99 is a special case, where the signal strength is unknown.
- final int asu = signalStrength.getGsmSignalStrength();
- if (asu <= 2 || asu == 99) return SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- else if (asu >= GSM_SIGNAL_STRENGTH_GREAT) return SIGNAL_STRENGTH_GREAT;
- else if (asu >= GSM_SIGNAL_STRENGTH_GOOD) return SIGNAL_STRENGTH_GOOD;
- else if (asu >= GSM_SIGNAL_STRENGTH_MODERATE) return SIGNAL_STRENGTH_MODERATE;
- else return SIGNAL_STRENGTH_POOR;
- } else {
- // From frameworks/base/telephony/java/android/telephony/CellSignalStrengthCdma.java
-
- final int cdmaLevel = getCdmaLevel(signalStrength);
- final int evdoLevel = getEvdoLevel(signalStrength);
- if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- /* We don't know evdo, use cdma */
- return getCdmaLevel(signalStrength);
- } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- /* We don't know cdma, use evdo */
- return getEvdoLevel(signalStrength);
- } else {
- /* We know both, use the lowest level */
- return cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
- }
- }
- }
-
- /**
- * Get cdma as level 0..4
- */
- private static int getCdmaLevel(final SignalStrength signalStrength) {
- final int cdmaDbm = signalStrength.getCdmaDbm();
- final int cdmaEcio = signalStrength.getCdmaEcio();
- int levelDbm;
- int levelEcio;
- if (cdmaDbm >= -75) levelDbm = SIGNAL_STRENGTH_GREAT;
- else if (cdmaDbm >= -85) levelDbm = SIGNAL_STRENGTH_GOOD;
- else if (cdmaDbm >= -95) levelDbm = SIGNAL_STRENGTH_MODERATE;
- else if (cdmaDbm >= -100) levelDbm = SIGNAL_STRENGTH_POOR;
- else levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- // Ec/Io are in dB*10
- if (cdmaEcio >= -90) levelEcio = SIGNAL_STRENGTH_GREAT;
- else if (cdmaEcio >= -110) levelEcio = SIGNAL_STRENGTH_GOOD;
- else if (cdmaEcio >= -130) levelEcio = SIGNAL_STRENGTH_MODERATE;
- else if (cdmaEcio >= -150) levelEcio = SIGNAL_STRENGTH_POOR;
- else levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- final int level = (levelDbm < levelEcio) ? levelDbm : levelEcio;
- return level;
- }
- /**
- * Get Evdo as level 0..4
- */
- private static int getEvdoLevel(final SignalStrength signalStrength) {
- final int evdoDbm = signalStrength.getEvdoDbm();
- final int evdoSnr = signalStrength.getEvdoSnr();
- int levelEvdoDbm;
- int levelEvdoSnr;
- if (evdoDbm >= -65) levelEvdoDbm = SIGNAL_STRENGTH_GREAT;
- else if (evdoDbm >= -75) levelEvdoDbm = SIGNAL_STRENGTH_GOOD;
- else if (evdoDbm >= -90) levelEvdoDbm = SIGNAL_STRENGTH_MODERATE;
- else if (evdoDbm >= -105) levelEvdoDbm = SIGNAL_STRENGTH_POOR;
- else levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- if (evdoSnr >= 7) levelEvdoSnr = SIGNAL_STRENGTH_GREAT;
- else if (evdoSnr >= 5) levelEvdoSnr = SIGNAL_STRENGTH_GOOD;
- else if (evdoSnr >= 3) levelEvdoSnr = SIGNAL_STRENGTH_MODERATE;
- else if (evdoSnr >= 1) levelEvdoSnr = SIGNAL_STRENGTH_POOR;
- else levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- final int level = (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr;
- return level;
- }
-}
diff --git a/src/com/android/messaging/util/ContactRecipientEntryUtils.java b/src/com/android/messaging/util/ContactRecipientEntryUtils.java
deleted file mode 100644
index 78c6ffd..0000000
--- a/src/com/android/messaging/util/ContactRecipientEntryUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.net.Uri;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.text.TextUtils;
-
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.BugleRecipientEntry;
-import com.android.messaging.datamodel.data.ParticipantData;
-
-/**
- * Provides utility methods around creating RecipientEntry instance specific to Bugle's needs.
- */
-public class ContactRecipientEntryUtils {
- /**
- * A special contact id for generated contacts with no display name (number only) and avatar.
- * By default, the chips UI doesn't load any avatar for chips with no display name, or where
- * the display name is the same as phone number (which is true for unknown contacts).
- * Since Bugle always generate a default avatar for all contacts, this is used to replace
- * those default generated chips with a phone number and no avatars.
- */
- private static final long CONTACT_ID_NUMBER_WITH_AVATAR = -1000;
-
- /**
- * A generated special contact which says "Send to xxx" in the contact list, which allows
- * a user to direct send an SMS to a number that was manually typed in.
- */
- private static final long CONTACT_ID_SENDTO_DESTINATION = -1001;
-
- /**
- * Construct a special "Send to xxx" entry for a given destination.
- */
- public static RecipientEntry constructSendToDestinationEntry(final String destination) {
- return constructSpecialRecipientEntry(destination, CONTACT_ID_SENDTO_DESTINATION);
- }
-
- /**
- * Construct a generated contact entry but with rendered avatar.
- */
- public static RecipientEntry constructNumberWithAvatarEntry(final String destination) {
- return constructSpecialRecipientEntry(destination, CONTACT_ID_NUMBER_WITH_AVATAR);
- }
-
- private static RecipientEntry constructSpecialRecipientEntry(final String destination,
- final long contactId) {
- // For the send-to-destination (e.g. "Send to xxx" in the auto-complete drop-down)
- // we want to show a default avatar with a static background so that it doesn't flicker
- // as the user types.
- final Uri avatarUri = contactId == CONTACT_ID_SENDTO_DESTINATION ?
- AvatarUriUtil.DEFAULT_BACKGROUND_AVATAR : null;
- return BugleRecipientEntry.constructTopLevelEntry(null, DisplayNameSources.STRUCTURED_NAME,
- destination, RecipientEntry.INVALID_DESTINATION_TYPE, null, contactId,
- null, contactId, avatarUri, true, null);
- }
-
- /**
- * Gets the display name for contact list only. For most cases this is the same as the normal
- * contact name, but there are cases where these two differ. For example, for the
- * send to typed number item, we'd like to show "Send to xxx" in the contact list. However,
- * when this item is actually added to the chips edit box, we would like to show just the
- * phone number (i.e. no display name).
- */
- public static String getDisplayNameForContactList(final RecipientEntry entry) {
- if (entry.getContactId() == CONTACT_ID_SENDTO_DESTINATION) {
- return Factory.get().getApplicationContext().getResources().getString(
- R.string.contact_list_send_to_text, formatDestination(entry));
- } else if (!TextUtils.isEmpty(entry.getDisplayName())) {
- return entry.getDisplayName();
- } else {
- return formatDestination(entry);
- }
- }
-
- public static String formatDestination(final RecipientEntry entry) {
- return PhoneUtils.getDefault().formatForDisplay(entry.getDestination());
- }
-
- /**
- * Returns true if the given entry has only avatar and number
- */
- public static boolean isAvatarAndNumberOnlyContact(final RecipientEntry entry) {
- return entry.getContactId() == CONTACT_ID_NUMBER_WITH_AVATAR;
- }
-
- /**
- * Returns true if the given entry is a special send to number item.
- */
- public static boolean isSendToDestinationContact(final RecipientEntry entry) {
- return entry.getContactId() == CONTACT_ID_SENDTO_DESTINATION;
- }
-
- /**
- * Returns true if the given participant is a special send to number item.
- */
- public static boolean isSendToDestinationContact(final ParticipantData participant) {
- return participant.getContactId() == CONTACT_ID_SENDTO_DESTINATION;
- }
-}
diff --git a/src/com/android/messaging/util/ContactUtil.java b/src/com/android/messaging/util/ContactUtil.java
deleted file mode 100644
index 8555889..0000000
--- a/src/com/android/messaging/util/ContactUtil.java
+++ /dev/null
@@ -1,525 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.PhoneLookup;
-import android.provider.ContactsContract.Profile;
-import android.text.TextUtils;
-import android.view.View;
-
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.CursorQueryData;
-import com.android.messaging.datamodel.FrequentContactsCursorQueryData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Utility class including logic to list, filter, and lookup phone and emails in CP2.
- */
-@VisibleForTesting
-public class ContactUtil {
-
- /**
- * Index of different columns in phone or email queries. All queries below should confirm to
- * this column content and ordering so that caller can use the uniformed way to process
- * returned cursors.
- */
- public static final int INDEX_CONTACT_ID = 0;
- public static final int INDEX_DISPLAY_NAME = 1;
- public static final int INDEX_PHOTO_URI = 2;
- public static final int INDEX_PHONE_EMAIL = 3;
- public static final int INDEX_PHONE_EMAIL_TYPE = 4;
- public static final int INDEX_PHONE_EMAIL_LABEL = 5;
-
- // An optional lookup_id column used by PhoneLookupQuery that is needed when querying for
- // contact information.
- public static final int INDEX_LOOKUP_KEY = 6;
-
- // An optional _id column to query results that need to be displayed in a list view.
- public static final int INDEX_DATA_ID = 7;
-
- // An optional sort_key column for displaying contact section labels.
- public static final int INDEX_SORT_KEY = 8;
-
- // Lookup key column index specific to frequent contacts query.
- public static final int INDEX_LOOKUP_KEY_FREQUENT = 3;
-
- /**
- * Constants for listing and filtering phones.
- */
- public static class PhoneQuery {
- public static final String SORT_KEY = Phone.SORT_KEY_PRIMARY;
-
- public static final String[] PROJECTION = new String[] {
- Phone.CONTACT_ID, // 0
- Phone.DISPLAY_NAME_PRIMARY, // 1
- Phone.PHOTO_THUMBNAIL_URI, // 2
- Phone.NUMBER, // 3
- Phone.TYPE, // 4
- Phone.LABEL, // 5
- Phone.LOOKUP_KEY, // 6
- Phone._ID, // 7
- PhoneQuery.SORT_KEY, // 8
- };
- }
-
- /**
- * Constants for looking up phone numbers.
- */
- public static class PhoneLookupQuery {
- public static final String[] PROJECTION = new String[] {
- // The _ID field points to the contact id of the content
- PhoneLookup._ID, // 0
- PhoneLookup.DISPLAY_NAME, // 1
- PhoneLookup.PHOTO_THUMBNAIL_URI, // 2
- PhoneLookup.NUMBER, // 3
- PhoneLookup.TYPE, // 4
- PhoneLookup.LABEL, // 5
- PhoneLookup.LOOKUP_KEY, // 6
- // The data id is not included as part of the projection since it's not part of
- // PhoneLookup. This is okay because the _id field serves as both the data id and
- // contact id. Also we never show the results directly in a list view so we are not
- // concerned about duplicated _id's (namely, the same contact has two same phone
- // numbers)
- };
- }
-
- public static class FrequentContactQuery {
- public static final String[] PROJECTION = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME, // 1
- Contacts.PHOTO_URI, // 2
- Phone.LOOKUP_KEY, // 3
- };
- }
-
- /**
- * Constants for listing and filtering emails.
- */
- public static class EmailQuery {
- public static final String SORT_KEY = Email.SORT_KEY_PRIMARY;
-
- public static final String[] PROJECTION = new String[] {
- Email.CONTACT_ID, // 0
- Email.DISPLAY_NAME_PRIMARY, // 1
- Email.PHOTO_THUMBNAIL_URI, // 2
- Email.ADDRESS, // 3
- Email.TYPE, // 4
- Email.LABEL, // 5
- Email.LOOKUP_KEY, // 6
- Email._ID, // 7
- EmailQuery.SORT_KEY, // 8
- };
- }
-
- public static final int INDEX_SELF_QUERY_LOOKUP_KEY = 3;
-
- /**
- * Constants for querying self from CP2.
- */
- public static class SelfQuery {
- public static final String[] PROJECTION = new String[] {
- Profile._ID, // 0
- Profile.DISPLAY_NAME_PRIMARY, // 1
- Profile.PHOTO_THUMBNAIL_URI, // 2
- Profile.LOOKUP_KEY // 3
- // Phone number, type, label and data_id is not provided in this projection since
- // Profile CONTENT_URI doesn't include this information. Also, we don't need it
- // we just need the name and avatar url.
- };
- }
-
- public static class StructuredNameQuery {
- public static final String[] PROJECTION = new String[] {
- StructuredName.DISPLAY_NAME,
- StructuredName.GIVEN_NAME,
- StructuredName.FAMILY_NAME,
- StructuredName.PREFIX,
- StructuredName.MIDDLE_NAME,
- StructuredName.SUFFIX
- };
- }
-
- public static final int INDEX_STRUCTURED_NAME_DISPLAY_NAME = 0;
- public static final int INDEX_STRUCTURED_NAME_GIVEN_NAME = 1;
- public static final int INDEX_STRUCTURED_NAME_FAMILY_NAME = 2;
- public static final int INDEX_STRUCTURED_NAME_PREFIX = 3;
- public static final int INDEX_STRUCTURED_NAME_MIDDLE_NAME = 4;
- public static final int INDEX_STRUCTURED_NAME_SUFFIX = 5;
-
- public static final long INVALID_CONTACT_ID = -1;
-
- /**
- * This class is static. No need to create an instance.
- */
- private ContactUtil() {
- }
-
- /**
- * Shows a contact card or add to contacts dialog for the given contact info
- * @param view The view whose click triggered this to show
- * @param contactId The id of the contact in the android contacts DB
- * @param contactLookupKey The lookup key from contacts DB
- * @param avatarUri Uri to the avatar image if available
- * @param normalizedDestination The normalized phone number or email
- */
- public static void showOrAddContact(final View view, final long contactId,
- final String contactLookupKey, final Uri avatarUri,
- final String normalizedDestination) {
- if (contactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
- && !TextUtils.isEmpty(contactLookupKey)) {
- final Uri lookupUri =
- ContactsContract.Contacts.getLookupUri(contactId, contactLookupKey);
- ContactsContract.QuickContact.showQuickContact(view.getContext(), view, lookupUri,
- ContactsContract.QuickContact.MODE_LARGE, null);
- } else if (!TextUtils.isEmpty(normalizedDestination) && !TextUtils.equals(
- normalizedDestination, ParticipantData.getUnknownSenderDestination())) {
- final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog(
- view.getContext(), avatarUri, normalizedDestination);
- dialog.show();
- }
- }
-
- @VisibleForTesting
- public static CursorQueryData getSelf(final Context context) {
- if (!ContactUtil.hasReadContactsPermission()) {
- return CursorQueryData.getEmptyQueryData();
- }
- return new CursorQueryData(context, Profile.CONTENT_URI, SelfQuery.PROJECTION, null, null,
- null);
- }
-
- /**
- * Get a list of phones sorted by contact name. One contact may have multiple phones.
- * In that case, each phone will be returned as a separate record in the result cursor.
- */
- @VisibleForTesting
- public static CursorQueryData getPhones(final Context context) {
- if (!ContactUtil.hasReadContactsPermission()) {
- return CursorQueryData.getEmptyQueryData();
- }
-
- // The AOSP Contacts provider allows adding a ContactsContract.REMOVE_DUPLICATE_ENTRIES
- // query parameter that removes duplicate (raw) numbers. Unfortunately, we can't use that
- // because it causes the some phones' contacts provider to return incorrect sections.
- final Uri uri = Phone.CONTENT_URI.buildUpon().appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
- .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true")
- .build();
-
- return new CursorQueryData(context, uri, PhoneQuery.PROJECTION, null, null,
- PhoneQuery.SORT_KEY);
- }
-
- /**
- * Lookup a destination (phone, email). Supplied destination should be a relatively complete
- * one for this to succeed. PhoneLookup / EmailLookup URI will apply some smartness to do a
- * loose match to see whether there is a contact that matches this destination.
- */
- public static CursorQueryData lookupDestination(final Context context,
- final String destination) {
- if (MmsSmsUtils.isEmailAddress(destination)) {
- return ContactUtil.lookupEmail(context, destination);
- } else {
- return ContactUtil.lookupPhone(context, destination);
- }
- }
-
- /**
- * Returns whether the search text indicates an email based search or a phone number based one.
- */
- private static boolean shouldFilterForEmail(final String searchText) {
- return searchText != null && searchText.contains("@");
- }
-
- /**
- * Get a list of destinations (phone, email) matching the partial destination.
- */
- public static CursorQueryData filterDestination(final Context context,
- final String destination) {
- if (shouldFilterForEmail(destination)) {
- return ContactUtil.filterEmails(context, destination);
- } else {
- return ContactUtil.filterPhones(context, destination);
- }
- }
-
- /**
- * Get a list of phones matching a search criteria. The search may be on contact name or
- * phone number. In case search is on contact name, all matching contact's phone number
- * will be returned.
- * NOTE: This is visible for testing only, clients should only call filterDestination() since
- * we support email addresses as well.
- */
- @VisibleForTesting
- public static CursorQueryData filterPhones(final Context context, final String query) {
- if (!ContactUtil.hasReadContactsPermission()) {
- return CursorQueryData.getEmptyQueryData();
- }
-
- final Uri uri = Phone.CONTENT_FILTER_URI.buildUpon()
- .appendPath(query).appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
- .build();
-
- return new CursorQueryData(context, uri, PhoneQuery.PROJECTION, null, null,
- PhoneQuery.SORT_KEY);
- }
-
- /**
- * Lookup a phone based on a phone number. Supplied phone should be a relatively complete
- * phone number for this to succeed. PhoneLookup URI will apply some smartness to do a
- * loose match to see whether there is a contact that matches this phone.
- * NOTE: This is visible for testing only, clients should only call lookupDestination() since
- * we support email addresses as well.
- */
- @VisibleForTesting
- public static CursorQueryData lookupPhone(final Context context, final String phone) {
- if (!ContactUtil.hasReadContactsPermission()) {
- return CursorQueryData.getEmptyQueryData();
- }
-
- final Uri uri = getPhoneLookupUri().buildUpon()
- .appendPath(phone).build();
-
- return new CursorQueryData(context, uri, PhoneLookupQuery.PROJECTION, null, null, null);
- }
-
- /**
- * Get frequently contacted people. This queries for Contacts.CONTENT_STREQUENT_URI, which
- * includes both starred or frequently contacted people.
- */
- public static CursorQueryData getFrequentContacts(final Context context) {
- if (!ContactUtil.hasReadContactsPermission()) {
- return CursorQueryData.getEmptyQueryData();
- }
-
- return new FrequentContactsCursorQueryData(context, FrequentContactQuery.PROJECTION,
- null, null, null);
- }
-
- /**
- * Get a list of emails matching a search criteria. In Bugle, since email is not a common
- * usage scenario, we should only do email search after user typed in a query indicating
- * an intention to search by email (for example, "joe@").
- * NOTE: This is visible for testing only, clients should only call filterDestination() since
- * we support email addresses as well.
- */
- @VisibleForTesting
- public static CursorQueryData filterEmails(final Context context, final String query) {
- if (!ContactUtil.hasReadContactsPermission()) {
- return CursorQueryData.getEmptyQueryData();
- }
-
- final Uri uri = Email.CONTENT_FILTER_URI.buildUpon()
- .appendPath(query).appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
- .build();
-
- return new CursorQueryData(context, uri, EmailQuery.PROJECTION, null, null,
- EmailQuery.SORT_KEY);
- }
-
- /**
- * Lookup emails based a complete email address. Since there is no special logic needed for
- * email lookup, this simply calls filterEmails.
- * NOTE: This is visible for testing only, clients should only call lookupDestination() since
- * we support email addresses as well.
- */
- @VisibleForTesting
- public static CursorQueryData lookupEmail(final Context context, final String email) {
- if (!ContactUtil.hasReadContactsPermission()) {
- return CursorQueryData.getEmptyQueryData();
- }
-
- final Uri uri = getEmailContentLookupUri().buildUpon()
- .appendPath(email).appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
- .build();
-
- return new CursorQueryData(context, uri, EmailQuery.PROJECTION, null, null,
- EmailQuery.SORT_KEY);
- }
-
- /**
- * Looks up the structured name for a contact.
- *
- * @param primaryOnly If there are multiple raw contacts, set this flag to return only the
- * name used as the primary display name. Otherwise, this method returns all names.
- */
- private static CursorQueryData lookupStructuredName(final Context context, final long contactId,
- final boolean primaryOnly) {
- if (!ContactUtil.hasReadContactsPermission()) {
- return CursorQueryData.getEmptyQueryData();
- }
-
- // TODO: Handle enterprise contacts
- final Uri uri = ContactsContract.Contacts.CONTENT_URI.buildUpon()
- .appendPath(String.valueOf(contactId))
- .appendPath(ContactsContract.Contacts.Data.CONTENT_DIRECTORY).build();
-
- String selection = ContactsContract.Data.MIMETYPE + "=?";
- final String[] selectionArgs = {
- StructuredName.CONTENT_ITEM_TYPE
- };
- if (primaryOnly) {
- selection += " AND " + Contacts.DISPLAY_NAME_PRIMARY + "="
- + StructuredName.DISPLAY_NAME;
- }
-
- return new CursorQueryData(context, uri,
- StructuredNameQuery.PROJECTION, selection, selectionArgs, null);
- }
-
- /**
- * Looks up the first name for a contact. If there are multiple raw
- * contacts, this returns the name that is associated with the contact's
- * primary display name. The name is null when contact id does not exist
- * (possibly because it is a corp contact) or it does not have a first name.
- */
- public static String lookupFirstName(final Context context, final long contactId) {
- if (isEnterpriseContactId(contactId)) {
- return null;
- }
- String firstName = null;
- Cursor nameCursor = null;
- try {
- nameCursor = ContactUtil.lookupStructuredName(context, contactId, true)
- .performSynchronousQuery();
- if (nameCursor != null && nameCursor.moveToFirst()) {
- firstName = nameCursor.getString(ContactUtil.INDEX_STRUCTURED_NAME_GIVEN_NAME);
- }
- } finally {
- if (nameCursor != null) {
- nameCursor.close();
- }
- }
- return firstName;
- }
-
- /**
- * Creates a RecipientEntry from the provided data fields (from the contacts cursor).
- * @param firstLevel whether this item is the first entry of this contact in the list.
- */
- public static RecipientEntry createRecipientEntry(final String displayName,
- final int displayNameSource, final String destination, final int destinationType,
- final String destinationLabel, final long contactId, final String lookupKey,
- final long dataId, final String photoThumbnailUri, final boolean firstLevel) {
- if (firstLevel) {
- return RecipientEntry.constructTopLevelEntry(displayName, displayNameSource,
- destination, destinationType, destinationLabel, contactId, null, dataId,
- photoThumbnailUri, true, lookupKey);
- } else {
- return RecipientEntry.constructSecondLevelEntry(displayName, displayNameSource,
- destination, destinationType, destinationLabel, contactId, null, dataId,
- photoThumbnailUri, true, lookupKey);
- }
- }
-
- /**
- * Creates a RecipientEntry for PhoneQuery result. The result is then displayed in the
- * contact search drop down or as replacement chips in the chips edit box.
- */
- public static RecipientEntry createRecipientEntryForPhoneQuery(final Cursor cursor,
- final boolean isFirstLevel) {
- final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- final String displayName = cursor.getString(
- ContactUtil.INDEX_DISPLAY_NAME);
- final String photoThumbnailUri = cursor.getString(
- ContactUtil.INDEX_PHOTO_URI);
- final String destination = cursor.getString(
- ContactUtil.INDEX_PHONE_EMAIL);
- final int destinationType = cursor.getInt(
- ContactUtil.INDEX_PHONE_EMAIL_TYPE);
- final String destinationLabel = cursor.getString(
- ContactUtil.INDEX_PHONE_EMAIL_LABEL);
- final String lookupKey = cursor.getString(
- ContactUtil.INDEX_LOOKUP_KEY);
-
- // PhoneQuery uses the contact id as the data id ("_id").
- final long dataId = contactId;
-
- return createRecipientEntry(displayName,
- DisplayNameSources.STRUCTURED_NAME, destination, destinationType,
- destinationLabel, contactId, lookupKey, dataId, photoThumbnailUri,
- isFirstLevel);
- }
-
- /**
- * Returns if a given contact id is valid.
- */
- public static boolean isValidContactId(final long contactId) {
- return contactId >= 0;
- }
-
- /**
- * Returns if a given contact id belongs to managed profile.
- */
- public static boolean isEnterpriseContactId(final long contactId) {
- return isWorkProfileSupported()
- && ContactsContract.Contacts.isEnterpriseContactId(contactId);
- }
-
- /**
- * Returns if managed profile is supported.
- */
- public static boolean isWorkProfileSupported() {
- final PackageManager pm = Factory.get().getApplicationContext().getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
- }
-
- /**
- * Returns Email lookup uri that will query both primary and corp profile
- */
- private static Uri getEmailContentLookupUri() {
- if (isWorkProfileSupported() && OsUtil.isAtLeastM()) {
- // TODO: use Email.ENTERPRISE_CONTENT_LOOKUP_URI, which will be available in M SDK API
- return Uri.parse("content://com.android.contacts/data/emails/lookup_enterprise");
- }
- return Email.CONTENT_LOOKUP_URI;
- }
-
- /**
- * Returns PhoneLookup URI.
- */
- public static Uri getPhoneLookupUri() {
- // Apply it to M only
- if (isWorkProfileSupported() && OsUtil.isAtLeastM()) {
- return PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI;
- }
- return PhoneLookup.CONTENT_FILTER_URI;
- }
-
- public static boolean hasReadContactsPermission() {
- return OsUtil.hasPermission(Manifest.permission.READ_CONTACTS);
- }
-}
diff --git a/src/com/android/messaging/util/ContentType.java b/src/com/android/messaging/util/ContentType.java
deleted file mode 100644
index bb4a7b2..0000000
--- a/src/com/android/messaging/util/ContentType.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.messaging.util;
-
-import android.webkit.MimeTypeMap;
-
-public final class ContentType {
- public static String THREE_GPP_EXTENSION = "3gp";
- public static String VIDEO_MP4_EXTENSION = "mp4";
- // Default extension used when we don't know one.
- public static String DEFAULT_EXTENSION = "dat";
-
- public static final int TYPE_IMAGE = 0;
- public static final int TYPE_VIDEO = 1;
- public static final int TYPE_AUDIO = 2;
- public static final int TYPE_VCARD = 3;
- public static final int TYPE_OTHER = 4;
-
- public static final String ANY_TYPE = "*/*";
- public static final String MMS_MESSAGE = "application/vnd.wap.mms-message";
- // The phony content type for generic PDUs (e.g. ReadOrig.ind,
- // Notification.ind, Delivery.ind).
- public static final String MMS_GENERIC = "application/vnd.wap.mms-generic";
- public static final String MMS_MULTIPART_MIXED = "application/vnd.wap.multipart.mixed";
- public static final String MMS_MULTIPART_RELATED = "application/vnd.wap.multipart.related";
- public static final String MMS_MULTIPART_ALTERNATIVE =
- "application/vnd.wap.multipart.alternative";
-
- public static final String TEXT_PLAIN = "text/plain";
- public static final String TEXT_HTML = "text/html";
- public static final String TEXT_VCALENDAR = "text/x-vCalendar";
- public static final String TEXT_VCARD = "text/x-vCard";
-
- public static final String IMAGE_PREFIX = "image/";
- public static final String IMAGE_UNSPECIFIED = "image/*";
- public static final String IMAGE_JPEG = "image/jpeg";
- public static final String IMAGE_JPG = "image/jpg";
- public static final String IMAGE_GIF = "image/gif";
- public static final String IMAGE_WBMP = "image/vnd.wap.wbmp";
- public static final String IMAGE_PNG = "image/png";
- public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp";
-
- public static final String AUDIO_UNSPECIFIED = "audio/*";
- public static final String AUDIO_AAC = "audio/aac";
- public static final String AUDIO_AMR = "audio/amr";
- public static final String AUDIO_IMELODY = "audio/imelody";
- public static final String AUDIO_MID = "audio/mid";
- public static final String AUDIO_MIDI = "audio/midi";
- public static final String AUDIO_MP3 = "audio/mp3";
- public static final String AUDIO_MPEG3 = "audio/mpeg3";
- public static final String AUDIO_MPEG = "audio/mpeg";
- public static final String AUDIO_MPG = "audio/mpg";
- public static final String AUDIO_MP4 = "audio/mp4";
- public static final String AUDIO_MP4_LATM = "audio/mp4-latm";
- public static final String AUDIO_X_MID = "audio/x-mid";
- public static final String AUDIO_X_MIDI = "audio/x-midi";
- public static final String AUDIO_X_MP3 = "audio/x-mp3";
- public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3";
- public static final String AUDIO_X_MPEG = "audio/x-mpeg";
- public static final String AUDIO_X_MPG = "audio/x-mpg";
- public static final String AUDIO_3GPP = "audio/3gpp";
- public static final String AUDIO_X_WAV = "audio/x-wav";
- public static final String AUDIO_OGG = "application/ogg";
-
- public static final String MULTIPART_MIXED = "multipart/mixed";
-
- public static final String VIDEO_UNSPECIFIED = "video/*";
- public static final String VIDEO_3GP = "video/3gp";
- public static final String VIDEO_3GPP = "video/3gpp";
- public static final String VIDEO_3G2 = "video/3gpp2";
- public static final String VIDEO_H263 = "video/h263";
- public static final String VIDEO_M4V = "video/m4v";
- public static final String VIDEO_MP4 = "video/mp4";
- public static final String VIDEO_MPEG = "video/mpeg";
- public static final String VIDEO_MPEG4 = "video/mpeg4";
- public static final String VIDEO_WEBM = "video/webm";
-
- public static final String APP_SMIL = "application/smil";
- public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml";
- public static final String APP_XHTML = "application/xhtml+xml";
-
- public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content";
- public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message";
-
- // This class should never be instantiated.
- private ContentType() {
- }
-
- public static boolean isTextType(final String contentType) {
- return TEXT_PLAIN.equals(contentType)
- || TEXT_HTML.equals(contentType)
- || APP_WAP_XHTML.equals(contentType);
- }
-
- public static boolean isMediaType(final String contentType) {
- return isImageType(contentType)
- || isVideoType(contentType)
- || isAudioType(contentType)
- || isVCardType(contentType);
- }
-
- public static boolean isImageType(final String contentType) {
- return (null != contentType) && contentType.startsWith(IMAGE_PREFIX);
- }
-
- public static boolean isAudioType(final String contentType) {
- return (null != contentType) &&
- (contentType.startsWith("audio/") || contentType.equalsIgnoreCase(AUDIO_OGG));
- }
-
- public static boolean isVideoType(final String contentType) {
- return (null != contentType) && contentType.startsWith("video/");
- }
-
- public static boolean isVCardType(final String contentType) {
- return (null != contentType) && contentType.equalsIgnoreCase(TEXT_VCARD);
- }
-
- public static boolean isDrmType(final String contentType) {
- return (null != contentType)
- && (contentType.equals(APP_DRM_CONTENT)
- || contentType.equals(APP_DRM_MESSAGE));
- }
-
- public static boolean isUnspecified(final String contentType) {
- return (null != contentType) && contentType.endsWith("*");
- }
-
- /**
- * If the content type is a type which can be displayed in the conversation list as a preview.
- */
- public static boolean isConversationListPreviewableType(final String contentType) {
- return ContentType.isAudioType(contentType) || ContentType.isVideoType(contentType) ||
- ContentType.isImageType(contentType) || ContentType.isVCardType(contentType);
- }
-
- /**
- * Given a filename, look at the extension and try and determine the mime type.
- *
- * @param fileName a filename to determine the type from, such as img1231.jpg
- * @param contentTypeDefault type to use when the content type can't be determined from the file
- * extension. It can be null or a type such as ContentType.IMAGE_UNSPECIFIED
- * @return Content type of the extension.
- */
- public static String getContentTypeFromExtension(final String fileName,
- final String contentTypeDefault) {
- final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
- final String extension = MimeTypeMap.getFileExtensionFromUrl(fileName);
- String contentType = mimeTypeMap.getMimeTypeFromExtension(extension);
- if (contentType == null) {
- contentType = contentTypeDefault;
- }
- return contentType;
- }
-
- /**
- * Get the common file extension for a given content type
- * @param contentType The content type
- * @return The extension without the .
- */
- public static String getExtension(final String contentType) {
- if (VIDEO_MP4.equals(contentType)) {
- return VIDEO_MP4_EXTENSION;
- } else if (VIDEO_3GPP.equals(contentType)) {
- return THREE_GPP_EXTENSION;
- } else {
- return DEFAULT_EXTENSION;
- }
- }
-}
diff --git a/src/com/android/messaging/util/ConversationIdSet.java b/src/com/android/messaging/util/ConversationIdSet.java
deleted file mode 100644
index 75bf634..0000000
--- a/src/com/android/messaging/util/ConversationIdSet.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-
-/**
- * Utility class to make it easy to store multiple conversation id strings in a single string
- * with delimeters.
- */
-public class ConversationIdSet extends HashSet<String> {
- private static final String JOIN_DELIMITER = "|";
- private static final String SPLIT_DELIMITER = "\\|";
-
- public ConversationIdSet() {
- super();
- }
-
- public ConversationIdSet(final Collection<String> asList) {
- super(asList);
- }
-
- public String first() {
- if (size() > 0) {
- return iterator().next();
- } else {
- return null;
- }
- }
-
- public static ConversationIdSet createSet(final String conversationIdSetString) {
- ConversationIdSet set = null;
- if (conversationIdSetString != null) {
- set = new ConversationIdSet(Arrays.asList(conversationIdSetString.split(
- SPLIT_DELIMITER)));
- }
- return set;
- }
-
- public String getDelimitedString() {
- return OsUtil.joinFromSetWithDelimiter(this, JOIN_DELIMITER);
- }
-
- public static String join(final String conversationIdSet1, final String conversationIdSet2) {
- String joined = null;
- if (conversationIdSet1 == null) {
- joined = conversationIdSet2;
- } else if (conversationIdSet2 != null) {
- joined = conversationIdSet1 + JOIN_DELIMITER + conversationIdSet2;
- }
- return joined;
- }
-
-}
diff --git a/src/com/android/messaging/util/CubicBezierInterpolator.java b/src/com/android/messaging/util/CubicBezierInterpolator.java
deleted file mode 100644
index 317b3e6..0000000
--- a/src/com/android/messaging/util/CubicBezierInterpolator.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.view.animation.Interpolator;
-
-/**
- * Class that acts as an interpolator to match the cubic-bezier css timing function where p0 is
- * fixed at 0,0 and p3 is fixed at 1,1
- */
-public class CubicBezierInterpolator implements Interpolator {
- private final float mX1;
- private final float mY1;
- private final float mX2;
- private final float mY2;
-
- public CubicBezierInterpolator(final float x1, final float y1, final float x2, final float y2) {
- mX1 = x1;
- mY1 = y1;
- mX2 = x2;
- mY2 = y2;
- }
-
- @Override
- public float getInterpolation(float v) {
- return getY(getTForXValue(v));
- }
-
- private float getX(final float t) {
- return getCoordinate(t, mX1, mX2);
- }
-
- private float getY(final float t) {
- return getCoordinate(t, mY1, mY2);
- }
-
- private float getCoordinate(float t, float p1, float p2) {
- // Special case start and end.
- if (t == 0.0f || t == 1.0f) {
- return t;
- }
-
- // Step one - from 4 points to 3.
- float ip0 = linearInterpolate(0, p1, t);
- float ip1 = linearInterpolate(p1, p2, t);
- float ip2 = linearInterpolate(p2, 1, t);
-
- // Step two - from 3 points to 2.
- ip0 = linearInterpolate(ip0, ip1, t);
- ip1 = linearInterpolate(ip1, ip2, t);
-
- // Final step - last point.
- return linearInterpolate(ip0, ip1, t);
- }
-
- private float linearInterpolate(float a, float b, float progress) {
- return a + (b - a) * progress;
- }
-
- private float getTForXValue(final float x) {
- final float epsilon = 1e-6f;
- final int iterations = 8;
-
- if (x <= 0.0f) {
- return 0.0f;
- } else if (x >= 1.0f) {
- return 1.0f;
- }
-
- // Try gradient descent to solve for t. If it works, it is very fast.
- float t = x;
- float minT = 0.0f;
- float maxT = 1.0f;
- float value = 0.0f;
- for (int i = 0; i < iterations; i++) {
- value = getX(t);
- double derivative = (getX(t + epsilon) - value) / epsilon;
- if (Math.abs(value - x) < epsilon) {
- return t;
- } else if (Math.abs(derivative) < epsilon) {
- break;
- } else {
- if (value < x) {
- minT = t;
- } else {
- maxT = t;
- }
- t -= (value - x) / derivative;
- }
- }
-
- // If the gradient descent got stuck in a local minimum, e.g. because the
- // derivative was close to 0, use an interval bisection instead.
- for (int i = 0; Math.abs(value - x) > epsilon && i < iterations; i++) {
- if (value < x) {
- minT = t;
- t = (t + maxT) / 2.0f;
- } else {
- maxT = t;
- t = (t + minT) / 2.0f;
- }
- value = getX(t);
- }
- return t;
- }
-}
diff --git a/src/com/android/messaging/util/Dates.java b/src/com/android/messaging/util/Dates.java
deleted file mode 100644
index d012dfd..0000000
--- a/src/com/android/messaging/util/Dates.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-/**
- * Collection of date utilities.
- */
-public class Dates {
- public static final long SECOND_IN_MILLIS = 1000;
- public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
- public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
- public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
- public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
-
- // Flags to specify whether or not to use 12 or 24 hour mode.
- // Callers of methods in this class should never have to specify these; this is really
- // intended only for unit tests.
- @SuppressWarnings("deprecation")
- @VisibleForTesting public static final int FORCE_12_HOUR = DateUtils.FORMAT_12HOUR;
- @SuppressWarnings("deprecation")
- @VisibleForTesting public static final int FORCE_24_HOUR = DateUtils.FORMAT_24HOUR;
-
- /**
- * Private default constructor
- */
- private Dates() {
- }
-
- private static Context getContext() {
- return Factory.get().getApplicationContext();
- }
- /**
- * Get the relative time as a string
- *
- * @param time The time
- *
- * @return The relative time
- */
- public static CharSequence getRelativeTimeSpanString(final long time) {
- final long now = System.currentTimeMillis();
- if (now - time < DateUtils.MINUTE_IN_MILLIS) {
- // Also fixes bug where posts appear in the future
- return getContext().getResources().getText(R.string.posted_just_now);
- }
-
- // Workaround for b/5657035. The platform method {@link DateUtils#getRelativeTimeSpan()}
- // passes a null context to other platform methods. However, on some devices, this
- // context is dereferenced when it shouldn't be and an NPE is thrown. We catch that
- // here and use a slightly less precise time.
- try {
- return DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE).toString();
- } catch (final NullPointerException npe) {
- return getShortRelativeTimeSpanString(time);
- }
- }
-
- public static CharSequence getConversationTimeString(final long time) {
- return getTimeString(time, true /*abbreviated*/, false /*minPeriodToday*/);
- }
-
- public static CharSequence getMessageTimeString(final long time) {
- return getTimeString(time, false /*abbreviated*/, false /*minPeriodToday*/);
- }
-
- public static CharSequence getWidgetTimeString(final long time, final boolean abbreviated) {
- return getTimeString(time, abbreviated, true /*minPeriodToday*/);
- }
-
- public static CharSequence getFastScrollPreviewTimeString(final long time) {
- return getTimeString(time, true /* abbreviated */, true /* minPeriodToday */);
- }
-
- public static CharSequence getMessageDetailsTimeString(final long time) {
- final Context context = getContext();
- int flags;
- if (android.text.format.DateFormat.is24HourFormat(context)) {
- flags = FORCE_24_HOUR;
- } else {
- flags = FORCE_12_HOUR;
- }
- return getOlderThanAYearTimestamp(time,
- context.getResources().getConfiguration().locale, false /*abbreviated*/,
- flags);
- }
-
- private static CharSequence getTimeString(final long time, final boolean abbreviated,
- final boolean minPeriodToday) {
- final Context context = getContext();
- int flags;
- if (android.text.format.DateFormat.is24HourFormat(context)) {
- flags = FORCE_24_HOUR;
- } else {
- flags = FORCE_12_HOUR;
- }
- return getTimestamp(time, System.currentTimeMillis(), abbreviated,
- context.getResources().getConfiguration().locale, flags, minPeriodToday);
- }
-
- @VisibleForTesting
- public static CharSequence getTimestamp(final long time, final long now,
- final boolean abbreviated, final Locale locale, final int flags,
- final boolean minPeriodToday) {
- final long timeDiff = now - time;
-
- if (!minPeriodToday && timeDiff < DateUtils.MINUTE_IN_MILLIS) {
- return getLessThanAMinuteOldTimeString(abbreviated);
- } else if (!minPeriodToday && timeDiff < DateUtils.HOUR_IN_MILLIS) {
- return getLessThanAnHourOldTimeString(timeDiff, flags);
- } else if (getNumberOfDaysPassed(time, now) == 0) {
- return getTodayTimeStamp(time, flags);
- } else if (timeDiff < DateUtils.WEEK_IN_MILLIS) {
- return getThisWeekTimestamp(time, locale, abbreviated, flags);
- } else if (timeDiff < DateUtils.YEAR_IN_MILLIS) {
- return getThisYearTimestamp(time, locale, abbreviated, flags);
- } else {
- return getOlderThanAYearTimestamp(time, locale, abbreviated, flags);
- }
- }
-
- private static CharSequence getLessThanAMinuteOldTimeString(
- final boolean abbreviated) {
- return getContext().getResources().getText(
- abbreviated ? R.string.posted_just_now : R.string.posted_now);
- }
-
- private static CharSequence getLessThanAnHourOldTimeString(final long timeDiff,
- final int flags) {
- final long count = (timeDiff / MINUTE_IN_MILLIS);
- final String format = getContext().getResources().getQuantityString(
- R.plurals.num_minutes_ago, (int) count);
- return String.format(format, count);
- }
-
- private static CharSequence getTodayTimeStamp(final long time, final int flags) {
- return DateUtils.formatDateTime(getContext(), time,
- DateUtils.FORMAT_SHOW_TIME | flags);
- }
-
- private static CharSequence getExplicitFormattedTime(final long time, final int flags,
- final String format24, final String format12) {
- SimpleDateFormat formatter;
- if ((flags & FORCE_24_HOUR) == FORCE_24_HOUR) {
- formatter = new SimpleDateFormat(format24);
- } else {
- formatter = new SimpleDateFormat(format12);
- }
- return formatter.format(new Date(time));
- }
-
- private static CharSequence getThisWeekTimestamp(final long time,
- final Locale locale, final boolean abbreviated, final int flags) {
- final Context context = getContext();
- if (abbreviated) {
- return DateUtils.formatDateTime(context, time,
- DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY | flags);
- } else {
- if (locale.equals(Locale.US)) {
- return getExplicitFormattedTime(time, flags, "EEE HH:mm", "EEE h:mmaa");
- } else {
- return DateUtils.formatDateTime(context, time,
- DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_TIME
- | DateUtils.FORMAT_ABBREV_WEEKDAY
- | flags);
- }
- }
- }
-
- private static CharSequence getThisYearTimestamp(final long time, final Locale locale,
- final boolean abbreviated, final int flags) {
- final Context context = getContext();
- if (abbreviated) {
- return DateUtils.formatDateTime(context, time,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH
- | DateUtils.FORMAT_NO_YEAR | flags);
- } else {
- if (locale.equals(Locale.US)) {
- return getExplicitFormattedTime(time, flags, "MMM d, HH:mm", "MMM d, h:mmaa");
- } else {
- return DateUtils.formatDateTime(context, time,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME
- | DateUtils.FORMAT_ABBREV_MONTH
- | DateUtils.FORMAT_NO_YEAR
- | flags);
- }
- }
- }
-
- private static CharSequence getOlderThanAYearTimestamp(final long time,
- final Locale locale, final boolean abbreviated, final int flags) {
- final Context context = getContext();
- if (abbreviated) {
- if (locale.equals(Locale.US)) {
- return getExplicitFormattedTime(time, flags, "M/d/yy", "M/d/yy");
- } else {
- return DateUtils.formatDateTime(context, time,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
- | DateUtils.FORMAT_NUMERIC_DATE);
- }
- } else {
- if (locale.equals(Locale.US)) {
- return getExplicitFormattedTime(time, flags, "M/d/yy, HH:mm", "M/d/yy, h:mmaa");
- } else {
- return DateUtils.formatDateTime(context, time,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME
- | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_YEAR
- | flags);
- }
- }
- }
-
- public static CharSequence getShortRelativeTimeSpanString(final long time) {
- final long now = System.currentTimeMillis();
- final long duration = Math.abs(now - time);
-
- int resId;
- long count;
-
- final Context context = getContext();
-
- if (duration < HOUR_IN_MILLIS) {
- count = duration / MINUTE_IN_MILLIS;
- resId = R.plurals.num_minutes_ago;
- } else if (duration < DAY_IN_MILLIS) {
- count = duration / HOUR_IN_MILLIS;
- resId = R.plurals.num_hours_ago;
- } else if (duration < WEEK_IN_MILLIS) {
- count = getNumberOfDaysPassed(time, now);
- resId = R.plurals.num_days_ago;
- } else {
- // Although we won't be showing a time, there is a bug on some devices that use
- // the passed in context. On these devices, passing in a {@code null} context
- // here will generate an NPE. See b/5657035.
- return DateUtils.formatDateRange(context, time, time,
- DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_ABBREV_RELATIVE);
- }
-
- final String format = context.getResources().getQuantityString(resId, (int) count);
- return String.format(format, count);
- }
-
- private static synchronized long getNumberOfDaysPassed(final long date1, final long date2) {
- if (sThenTime == null) {
- sThenTime = new Time();
- }
- sThenTime.set(date1);
- final int day1 = Time.getJulianDay(date1, sThenTime.gmtoff);
- sThenTime.set(date2);
- final int day2 = Time.getJulianDay(date2, sThenTime.gmtoff);
- return Math.abs(day2 - day1);
- }
-
- private static Time sThenTime;
-}
diff --git a/src/com/android/messaging/util/DebugUtils.java b/src/com/android/messaging/util/DebugUtils.java
deleted file mode 100644
index f2c1d65..0000000
--- a/src/com/android/messaging/util/DebugUtils.java
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.media.MediaPlayer;
-import android.os.Environment;
-import android.telephony.SmsMessage;
-import android.text.TextUtils;
-import android.widget.ArrayAdapter;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.action.DumpDatabaseAction;
-import com.android.messaging.datamodel.action.LogTelephonyDatabaseAction;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.debug.DebugSmsMmsFromDumpFileDialogFragment;
-import com.google.common.io.ByteStreams;
-
-import java.io.BufferedInputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.StreamCorruptedException;
-
-public class DebugUtils {
- private static final String TAG = "bugle.util.DebugUtils";
-
- private static boolean sDebugNoise;
- private static boolean sDebugClassZeroSms;
- private static MediaPlayer [] sMediaPlayer;
- private static final Object sLock = new Object();
-
- public static final int DEBUG_SOUND_SERVER_REQUEST = 0;
- public static final int DEBUG_SOUND_DB_OP = 1;
-
- public static void maybePlayDebugNoise(final Context context, final int sound) {
- if (sDebugNoise) {
- synchronized (sLock) {
- try {
- if (sMediaPlayer == null) {
- sMediaPlayer = new MediaPlayer[2];
- sMediaPlayer[DEBUG_SOUND_SERVER_REQUEST] =
- MediaPlayer.create(context, R.raw.server_request_debug);
- sMediaPlayer[DEBUG_SOUND_DB_OP] =
- MediaPlayer.create(context, R.raw.db_op_debug);
- sMediaPlayer[DEBUG_SOUND_DB_OP].setVolume(1.0F, 1.0F);
- sMediaPlayer[DEBUG_SOUND_SERVER_REQUEST].setVolume(0.3F, 0.3F);
- }
- if (sMediaPlayer[sound] != null) {
- sMediaPlayer[sound].start();
- }
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MediaPlayer exception", e);
- } catch (final SecurityException e) {
- LogUtil.e(TAG, "MediaPlayer exception", e);
- } catch (final IllegalStateException e) {
- LogUtil.e(TAG, "MediaPlayer exception", e);
- }
- }
- }
- }
-
- public static boolean isDebugEnabled() {
- return BugleGservices.get().getBoolean(BugleGservicesKeys.ENABLE_DEBUGGING_FEATURES,
- BugleGservicesKeys.ENABLE_DEBUGGING_FEATURES_DEFAULT);
- }
-
- public abstract static class DebugAction {
- String mTitle;
- public DebugAction(final String title) {
- mTitle = title;
- }
-
- @Override
- public String toString() {
- return mTitle;
- }
-
- public abstract void run();
- }
-
- public static void showDebugOptions(final Activity host) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(host);
-
- final ArrayAdapter<DebugAction> arrayAdapter = new ArrayAdapter<DebugAction>(
- host, android.R.layout.simple_list_item_1);
-
- arrayAdapter.add(new DebugAction("Dump Database") {
- @Override
- public void run() {
- DumpDatabaseAction.dumpDatabase();
- }
- });
-
- arrayAdapter.add(new DebugAction("Log Telephony Data") {
- @Override
- public void run() {
- LogTelephonyDatabaseAction.dumpDatabase();
- }
- });
-
- arrayAdapter.add(new DebugAction("Toggle Noise") {
- @Override
- public void run() {
- sDebugNoise = !sDebugNoise;
- }
- });
-
- arrayAdapter.add(new DebugAction("Force sync SMS") {
- @Override
- public void run() {
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, -1);
- SyncManager.forceSync();
- }
- });
-
- arrayAdapter.add(new DebugAction("Sync SMS") {
- @Override
- public void run() {
- SyncManager.sync();
- }
- });
-
- arrayAdapter.add(new DebugAction("Load SMS/MMS from dump file") {
- @Override
- public void run() {
- new DebugSmsMmsDumpTask(host,
- DebugSmsMmsFromDumpFileDialogFragment.ACTION_LOAD).executeOnThreadPool();
- }
- });
-
- arrayAdapter.add(new DebugAction("Email SMS/MMS dump file") {
- @Override
- public void run() {
- new DebugSmsMmsDumpTask(host,
- DebugSmsMmsFromDumpFileDialogFragment.ACTION_EMAIL).executeOnThreadPool();
- }
- });
-
- arrayAdapter.add(new DebugAction("MMS Config...") {
- @Override
- public void run() {
- UIIntents.get().launchDebugMmsConfigActivity(host);
- }
- });
-
- arrayAdapter.add(new DebugAction(sDebugClassZeroSms ? "Turn off Class 0 sms test" :
- "Turn on Class Zero test") {
- @Override
- public void run() {
- sDebugClassZeroSms = !sDebugClassZeroSms;
- }
- });
-
- builder.setAdapter(arrayAdapter,
- new android.content.DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface arg0, final int pos) {
- arrayAdapter.getItem(pos).run();
- }
- });
-
- builder.create().show();
- }
-
- /**
- * Task to list all the dump files and perform an action on it
- */
- private static class DebugSmsMmsDumpTask extends SafeAsyncTask<Void, Void, String[]> {
- private final String mAction;
- private final Activity mHost;
-
- public DebugSmsMmsDumpTask(final Activity host, final String action) {
- mHost = host;
- mAction = action;
- }
-
- @Override
- protected void onPostExecute(final String[] result) {
- if (result == null || result.length < 1) {
- return;
- }
- final FragmentManager fragmentManager = mHost.getFragmentManager();
- final FragmentTransaction ft = fragmentManager.beginTransaction();
- final DebugSmsMmsFromDumpFileDialogFragment dialog =
- DebugSmsMmsFromDumpFileDialogFragment.newInstance(result, mAction);
- dialog.show(fragmentManager, ""/*tag*/);
- }
-
- @Override
- protected String[] doInBackgroundTimed(final Void... params) {
- final File dir = DebugUtils.getDebugFilesDir();
- return dir.list(new FilenameFilter() {
- @Override
- public boolean accept(final File dir, final String filename) {
- return filename != null
- && ((mAction == DebugSmsMmsFromDumpFileDialogFragment.ACTION_EMAIL
- && filename.equals(DumpDatabaseAction.DUMP_NAME))
- || filename.startsWith(MmsUtils.MMS_DUMP_PREFIX)
- || filename.startsWith(MmsUtils.SMS_DUMP_PREFIX));
- }
- });
- }
- }
-
- /**
- * Dump the received raw SMS data into a file on external storage
- *
- * @param id The ID to use as part of the dump file name
- * @param messages The raw SMS data
- */
- public static void dumpSms(final long id, final android.telephony.SmsMessage[] messages,
- final String format) {
- try {
- final String dumpFileName = MmsUtils.SMS_DUMP_PREFIX + Long.toString(id);
- final File dumpFile = DebugUtils.getDebugFile(dumpFileName, true);
- if (dumpFile != null) {
- final FileOutputStream fos = new FileOutputStream(dumpFile);
- final DataOutputStream dos = new DataOutputStream(fos);
- try {
- final int chars = (TextUtils.isEmpty(format) ? 0 : format.length());
- dos.writeInt(chars);
- if (chars > 0) {
- dos.writeUTF(format);
- }
- dos.writeInt(messages.length);
- for (final android.telephony.SmsMessage message : messages) {
- final byte[] pdu = message.getPdu();
- dos.writeInt(pdu.length);
- dos.write(pdu, 0, pdu.length);
- }
- dos.flush();
- } finally {
- dos.close();
- ensureReadable(dumpFile);
- }
- }
- } catch (final IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "dumpSms: " + e, e);
- }
- }
-
- /**
- * Load MMS/SMS from the dump file
- */
- public static SmsMessage[] retreiveSmsFromDumpFile(final String dumpFileName) {
- SmsMessage[] messages = null;
- final File inputFile = DebugUtils.getDebugFile(dumpFileName, false);
- if (inputFile != null) {
- FileInputStream fis = null;
- DataInputStream dis = null;
- try {
- fis = new FileInputStream(inputFile);
- dis = new DataInputStream(fis);
-
- // SMS dump
- final int chars = dis.readInt();
- if (chars > 0) {
- final String format = dis.readUTF();
- }
- final int count = dis.readInt();
- final SmsMessage[] messagesTemp = new SmsMessage[count];
- for (int i = 0; i < count; i++) {
- final int length = dis.readInt();
- final byte[] pdu = new byte[length];
- dis.read(pdu, 0, length);
- messagesTemp[i] = SmsMessage.createFromPdu(pdu);
- }
- messages = messagesTemp;
- } catch (final FileNotFoundException e) {
- // Nothing to do
- } catch (final StreamCorruptedException e) {
- // Nothing to do
- } catch (final IOException e) {
- // Nothing to do
- } finally {
- if (dis != null) {
- try {
- dis.close();
- } catch (final IOException e) {
- // Nothing to do
- }
- }
- }
- }
- return messages;
- }
-
- public static File getDebugFile(final String fileName, final boolean create) {
- final File dir = getDebugFilesDir();
- final File file = new File(dir, fileName);
- if (create && file.exists()) {
- file.delete();
- }
- return file;
- }
-
- public static File getDebugFilesDir() {
- final File dir = Environment.getExternalStorageDirectory();
- return dir;
- }
-
- /**
- * Load MMS/SMS from the dump file
- */
- public static byte[] receiveFromDumpFile(final String dumpFileName) {
- byte[] data = null;
- try {
- final File inputFile = getDebugFile(dumpFileName, false);
- if (inputFile != null) {
- final FileInputStream fis = new FileInputStream(inputFile);
- final BufferedInputStream bis = new BufferedInputStream(fis);
- try {
- // dump file
- data = ByteStreams.toByteArray(bis);
- if (data == null || data.length < 1) {
- LogUtil.e(LogUtil.BUGLE_TAG, "receiveFromDumpFile: empty data");
- }
- } finally {
- bis.close();
- }
- }
- } catch (final IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "receiveFromDumpFile: " + e, e);
- }
- return data;
- }
-
- public static void ensureReadable(final File file) {
- if (file.exists()){
- file.setReadable(true, false);
- }
- }
-
- /**
- * Logs the name of the method that is currently executing, e.g. "MyActivity.onCreate". This is
- * useful for surgically adding logs for tracing execution while debugging.
- * <p>
- * NOTE: This method retrieves the current thread's stack trace, which adds runtime overhead.
- * However, this method is only executed on eng builds if DEBUG logs are loggable.
- */
- public static void logCurrentMethod(String tag) {
- if (!LogUtil.isLoggable(tag, LogUtil.DEBUG)) {
- return;
- }
- StackTraceElement caller = getCaller(1);
- if (caller == null) {
- return;
- }
- String className = caller.getClassName();
- // Strip off the package name
- int lastDot = className.lastIndexOf('.');
- if (lastDot > -1) {
- className = className.substring(lastDot + 1);
- }
- LogUtil.d(tag, className + "." + caller.getMethodName());
- }
-
- /**
- * Returns info about the calling method. The {@code depth} parameter controls how far back to
- * go. For example, if foo() calls bar(), and bar() calls getCaller(0), it returns info about
- * bar(). If bar() instead called getCaller(1), it would return info about foo(). And so on.
- * <p>
- * NOTE: This method retrieves the current thread's stack trace, which adds runtime overhead.
- * It should only be used in production where necessary to gather context about an error or
- * unexpected event (e.g. the {@link Assert} class uses it).
- *
- * @return stack frame information for the caller (if found); otherwise {@code null}.
- */
- public static StackTraceElement getCaller(int depth) {
- // If the signature of this method is changed, proguard.flags must be updated!
- if (depth < 0) {
- throw new IllegalArgumentException("depth cannot be negative");
- }
- StackTraceElement[] trace = Thread.currentThread().getStackTrace();
- if (trace == null || trace.length < (depth + 2)) {
- return null;
- }
- // The stack trace includes some methods we don't care about (e.g. this method).
- // Walk down until we find this method, and then back up to the caller we're looking for.
- for (int i = 0; i < trace.length - 1; i++) {
- String methodName = trace[i].getMethodName();
- if ("getCaller".equals(methodName)) {
- return trace[i + depth + 1];
- }
- }
- // Never found ourself in the stack?!
- return null;
- }
-
- /**
- * Returns a boolean indicating whether ClassZero debugging is enabled. If enabled, any received
- * sms is treated as if it were a class zero message and displayed by the ClassZeroActivity.
- */
- public static boolean debugClassZeroSmsEnabled() {
- return sDebugClassZeroSms;
- }
-}
diff --git a/src/com/android/messaging/util/EmailAddress.java b/src/com/android/messaging/util/EmailAddress.java
deleted file mode 100644
index 0c0dab9..0000000
--- a/src/com/android/messaging/util/EmailAddress.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import com.google.common.base.CharMatcher;
-
-/**
- * Parsing the email address
- */
-public final class EmailAddress {
- private static final CharMatcher ANY_WHITESPACE = CharMatcher.anyOf(
- " \t\n\r\f\u000B\u0085\u2028\u2029\u200D\uFFEF\uFFFD\uFFFE\uFFFF");
- private static final CharMatcher EMAIL_ALLOWED_CHARS = CharMatcher.inRange((char) 0, (char) 31)
- .or(CharMatcher.is((char) 127))
- .or(CharMatcher.anyOf(" @,:<>"))
- .negate();
-
- /**
- * Helper method that checks whether the input text is valid email address.
- * TODO: This creates a new EmailAddress object each time
- * Need to make it more lightweight by pulling out the validation code into a static method.
- */
- public static boolean isValidEmail(final String emailText) {
- return new EmailAddress(emailText).isValid();
- }
-
- /**
- * Parses the specified email address. Internationalized addresses are treated as invalid.
- *
- * @param emailString A string representing just an email address. It should
- * not contain any other tokens. <code>"Name&lt;foo@example.org>"</code> won't be valid.
- */
- public EmailAddress(final String emailString) {
- this(emailString, false);
- }
-
- /**
- * Parses the specified email address.
- *
- * @param emailString A string representing just an email address. It should
- * not contain any other tokens. <code>"Name&lt;foo@example.org>"</code> won't be valid.
- * @param i18n Accept an internationalized address if it is true.
- */
- public EmailAddress(final String emailString, final boolean i18n) {
- allowI18n = i18n;
- valid = parseEmail(emailString);
- }
-
- /**
- * Parses the specified email address. Internationalized addresses are treated as invalid.
- *
- * @param user A string representing the username in the email prior to the '@' symbol
- * @param host A string representing the host following the '@' symbol
- */
- public EmailAddress(final String user, final String host) {
- this(user, host, false);
- }
-
- /**
- * Parses the specified email address.
- *
- * @param user A string representing the username in the email prior to the '@' symbol
- * @param host A string representing the host following the '@' symbol
- * @param i18n Accept an internationalized address if it is true.
- */
- public EmailAddress(final String user, final String host, final boolean i18n) {
- allowI18n = i18n;
- this.user = user;
- setHost(host);
- }
-
- protected boolean parseEmail(final String emailString) {
- // check for null
- if (emailString == null) {
- return false;
- }
-
- // Check for an '@' character. Get the last one, in case the local part is
- // quoted. See http://b/1944742.
- final int atIndex = emailString.lastIndexOf('@');
- if ((atIndex <= 0) || // no '@' character in the email address
- // or @ on the first position
- (atIndex == (emailString.length() - 1))) { // last character, no host
- return false;
- }
-
- user = emailString.substring(0, atIndex);
- host = emailString.substring(atIndex + 1);
-
- return isValidInternal();
- }
-
- @Override
- public String toString() {
- return user + "@" + host;
- }
-
- /**
- * Ensure the email address is valid, conforming to current RFC2821 and
- * RFC2822 guidelines (although some iffy characters, like ! and ;, are
- * allowed because they are not technically prohibited in the RFC)
- */
- private boolean isValidInternal() {
- if ((user == null) || (host == null)) {
- return false;
- }
-
- if ((user.length() == 0) || (host.length() == 0)) {
- return false;
- }
-
- // check for white space in the host
- if (ANY_WHITESPACE.indexIn(host) >= 0) {
- return false;
- }
-
- // ensure the host is above the minimum length
- if (host.length() < 4) {
- return false;
- }
-
- final int firstDot = host.indexOf('.');
-
- // ensure host contains at least one dot
- if (firstDot == -1) {
- return false;
- }
-
- // check if the host contains two continuous dots.
- if (host.indexOf("..") >= 0) {
- return false;
- }
-
- // check if the first host char is a dot.
- if (host.charAt(0) == '.') {
- return false;
- }
-
- final int secondDot = host.indexOf(".", firstDot + 1);
-
- // if there's a dot at the end, there needs to be a second dot
- if (host.charAt(host.length() - 1) == '.' && secondDot == -1) {
- return false;
- }
-
- // Host must not have any disallowed characters; allowI18n dictates whether
- // host must be ASCII.
- if (!EMAIL_ALLOWED_CHARS.matchesAllOf(host)
- || (!allowI18n && !CharMatcher.ASCII.matchesAllOf(host))) {
- return false;
- }
-
- if (user.startsWith("\"")) {
- if (!isQuotedUserValid()) {
- return false;
- }
- } else {
- // check for white space in the user
- if (ANY_WHITESPACE.indexIn(user) >= 0) {
- return false;
- }
-
- // the user cannot contain two continuous dots
- if (user.indexOf("..") >= 0) {
- return false;
- }
-
- // User must not have any disallowed characters; allow I18n dictates whether
- // user must be ASCII.
- if (!EMAIL_ALLOWED_CHARS.matchesAllOf(user)
- || (!allowI18n && !CharMatcher.ASCII.matchesAllOf(user))) {
- return false;
- }
- }
- return true;
- }
-
- private boolean isQuotedUserValid() {
- final int limit = user.length() - 1;
- if (limit < 1 || !user.endsWith("\"")) {
- return false;
- }
-
- // Unusual loop bounds (looking only at characters between the outer quotes,
- // not at either quote character). Plus, i is manipulated within the loop.
- for (int i = 1; i < limit; ++i) {
- final char ch = user.charAt(i);
- if (ch == '"' || ch == 127
- // No non-whitespace control chars:
- || (ch < 32 && !ANY_WHITESPACE.matches(ch))
- // No non-ASCII chars, unless i18n is in effect:
- || (ch >= 128 && !allowI18n)) {
- return false;
- } else if (ch == '\\') {
- if (i + 1 < limit) {
- ++i; // Skip the quoted character
- } else {
- // We have a trailing backslash -- so it can't be quoting anything.
- return false;
- }
- }
- }
-
- return true;
- }
-
- @Override
- public boolean equals(final Object otherObject) {
- // Do an instance check first as an optimization.
- if (this == otherObject) {
- return true;
- }
- if (otherObject instanceof EmailAddress) {
- final EmailAddress otherAddress = (EmailAddress) otherObject;
- return toString().equals(otherAddress.toString());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- // Arbitrary hash code as a function of both host and user.
- return toString().hashCode();
- }
-
- // accessors
- public boolean isValid() {
- return valid;
- }
-
- public String getUser() {
- return user;
- }
-
- public String getHost() {
- return host;
- }
-
- // used to change the host on an email address and rechecks validity
-
- /**
- * Changes the host name of the email address and rechecks the address'
- * validity. Exercise caution when storing EmailAddress instances in
- * hash-keyed collections. Calling setHost() with a different host name will
- * change the return value of hashCode.
- *
- * @param hostName The new host name of the email address.
- */
- public void setHost(final String hostName) {
- host = hostName;
- valid = isValidInternal();
- }
-
- protected boolean valid = false;
- protected String user = null;
- protected String host = null;
- protected boolean allowI18n = false;
-}
diff --git a/src/com/android/messaging/util/FallbackStrategies.java b/src/com/android/messaging/util/FallbackStrategies.java
deleted file mode 100644
index 57c208f..0000000
--- a/src/com/android/messaging/util/FallbackStrategies.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Provides a generic and loose-coupled framework to execute one primary and multiple fallback
- * strategies for solving a given task.<p>
- * Basically, what would have been a nasty try-catch that's hard to separate and maintain:
- * <pre><code>
- * try {
- * // doSomething() that may fail.
- * } catch (Exception ex) {
- * try {
- * // fallback1() that may fail.
- * } catch (Exception ex2) {
- * try {
- * // fallback2() that may fail.
- * } catch (Exception ex3) {
- * // ...
- * }
- * }
- * }
- * </code></pre>
- * Now becomes:<br>
- * <pre><code>
- * FallbackStrategies
- * .startWith(something)
- * .thenTry(fallback1)
- * .thenTry(fallback2)
- * .execute();
- * </code></pre>
- */
-public class FallbackStrategies<Input, Output> {
- public interface Strategy<Input, Output> {
- Output execute(Input params) throws Exception;
- }
-
- private final List<Strategy<Input, Output>> mChainedStrategies;
-
- private FallbackStrategies(final Strategy<Input, Output> primaryStrategy) {
- mChainedStrategies = new ArrayList<Strategy<Input, Output>>();
- mChainedStrategies.add(primaryStrategy);
- }
-
- public static <Input, Output> FallbackStrategies<Input, Output> startWith(
- final Strategy<Input, Output> primaryStrategy) {
- return new FallbackStrategies<Input, Output>(primaryStrategy);
- }
-
- public FallbackStrategies<Input, Output> thenTry(final Strategy<Input, Output> strategy) {
- Assert.isFalse(mChainedStrategies.isEmpty());
- mChainedStrategies.add(strategy);
- return this;
- }
-
- public Output execute(final Input params) {
- final int count = mChainedStrategies.size();
- for (int i = 0; i < count; i++) {
- final Strategy<Input, Output> strategy = mChainedStrategies.get(i);
- try {
- // If succeeds, this will directly return.
- return strategy.execute(params);
- } catch (Exception ex) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Exceptions occured when executing strategy " +
- strategy + (i < count - 1 ?
- ", attempting fallback " + mChainedStrategies.get(i + 1) :
- ", and running out of fallbacks."), ex);
- // This will fall through and continue with the next strategy (if any).
- }
- }
- // Running out of strategies, return null.
- // TODO: Should this accept user-defined fallback value other than null?
- return null;
- }
-}
diff --git a/src/com/android/messaging/util/FileUtil.java b/src/com/android/messaging/util/FileUtil.java
deleted file mode 100644
index 7c47ae9..0000000
--- a/src/com/android/messaging/util/FileUtil.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.webkit.MimeTypeMap;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-public class FileUtil {
- /** Returns a new file name, ensuring that such a file does not already exist. */
- private static synchronized File getNewFile(File directory, String extension,
- String fileNameFormat) throws IOException {
- final Date date = new Date(System.currentTimeMillis());
- final SimpleDateFormat dateFormat = new SimpleDateFormat(fileNameFormat);
- final String numberedFileNameFormat = dateFormat.format(date) + "_%02d" + "." + extension;
- for (int i = 1; i <= 99; i++) { // Only save 99 of the same file name.
- final String newName = String.format(Locale.US, numberedFileNameFormat, i);
- File testFile = new File(directory, newName);
- if (!testFile.exists()) {
- testFile.createNewFile();
- return testFile;
- }
- }
- LogUtil.e(LogUtil.BUGLE_TAG, "Too many duplicate file names: " + numberedFileNameFormat);
- return null;
- }
-
- /**
- * Creates an unused name to use for creating a new file. The format happens to be similar
- * to that used by the Android camera application.
- *
- * @param directory directory that the file should be saved to
- * @param contentType of the media being saved
- * @return file name to be used for creating the new file. The caller is responsible for
- * actually creating the file.
- */
- public static File getNewFile(File directory, String contentType) throws IOException {
- MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
- String fileExtension = mimeTypeMap.getExtensionFromMimeType(contentType);
-
- final Context context = Factory.get().getApplicationContext();
- String fileNameFormat = context.getString(ContentType.isImageType(contentType)
- ? R.string.new_image_file_name_format : R.string.new_file_name_format);
- return getNewFile(directory, fileExtension, fileNameFormat);
- }
-
- /** Delete everything below and including root */
- public static void removeFileOrDirectory(File root) {
- removeFileOrDirectoryExcept(root, null);
- }
-
- /** Delete everything below and including root except for the given file */
- public static void removeFileOrDirectoryExcept(File root, File exclude) {
- if (root.exists()) {
- if (root.isDirectory()) {
- for (File file : root.listFiles()) {
- if (exclude == null || !file.equals(exclude)) {
- removeFileOrDirectoryExcept(file, exclude);
- }
- }
- root.delete();
- } else if (root.isFile()) {
- root.delete();
- }
- }
- }
-
- /**
- * Move all files and folders under a directory into the target.
- */
- public static void moveAllContentUnderDirectory(File sourceDir, File targetDir) {
- if (sourceDir.isDirectory() && targetDir.isDirectory()) {
- if (isSameOrSubDirectory(sourceDir, targetDir)) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Can't move directory content since the source " +
- "directory is a parent of the target");
- return;
- }
- for (File file : sourceDir.listFiles()) {
- if (file.isDirectory()) {
- final File dirTarget = new File(targetDir, file.getName());
- dirTarget.mkdirs();
- moveAllContentUnderDirectory(file, dirTarget);
- } else {
- try {
- final File fileTarget = new File(targetDir, file.getName());
- Files.move(file, fileTarget);
- } catch (IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Failed to move files", e);
- // Try proceed with the next file.
- }
- }
- }
- }
- }
-
- /**
- * Checks, whether the child directory is the same as, or a sub-directory of the base
- * directory.
- */
- private static boolean isSameOrSubDirectory(File base, File child) {
- try {
- base = base.getCanonicalFile();
- child = child.getCanonicalFile();
- File parentFile = child;
- while (parentFile != null) {
- if (base.equals(parentFile)) {
- return true;
- }
- parentFile = parentFile.getParentFile();
- }
- return false;
- } catch (IOException ex) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error while accessing file", ex);
- return false;
- }
- }
-}
diff --git a/src/com/android/messaging/util/GifTranscoder.java b/src/com/android/messaging/util/GifTranscoder.java
deleted file mode 100644
index 65413a0..0000000
--- a/src/com/android/messaging/util/GifTranscoder.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.text.format.Formatter;
-
-import com.google.common.base.Stopwatch;
-
-import java.io.File;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Compresses a GIF so it can be sent via MMS.
- * <p>
- * The entry point lives in its own class, we can defer loading the native GIF transcoding library
- * into memory until we actually need it.
- */
-public class GifTranscoder {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static int MIN_HEIGHT = 100;
- private static int MIN_WIDTH = 100;
-
- static {
- System.loadLibrary("giftranscode");
- }
-
- public static boolean transcode(Context context, String filePath, String outFilePath) {
- if (!isEnabled()) {
- return false;
- }
- final long inputSize = new File(filePath).length();
- Stopwatch stopwatch = Stopwatch.createStarted();
- final boolean success = transcodeInternal(filePath, outFilePath);
- stopwatch.stop();
- final long elapsedMs = stopwatch.elapsed(TimeUnit.MILLISECONDS);
- final long outputSize = new File(outFilePath).length();
- final float compression = (inputSize > 0) ? ((float) outputSize / inputSize) : 0;
-
- if (success) {
- LogUtil.i(TAG, String.format("Resized GIF (%s) in %d ms, %s => %s (%.0f%%)",
- LogUtil.sanitizePII(filePath),
- elapsedMs,
- Formatter.formatShortFileSize(context, inputSize),
- Formatter.formatShortFileSize(context, outputSize),
- compression * 100.0f));
- }
- return success;
- }
-
- private static native boolean transcodeInternal(String filePath, String outFilePath);
-
- /**
- * Estimates the size of a GIF transcoded from a GIF with the specified size.
- */
- public static long estimateFileSizeAfterTranscode(long fileSize) {
- // I tested transcoding on ~70 GIFs and found that the transcoded files are in general
- // about 25-35% the size of the original. This compression ratio is very consistent for the
- // class of GIFs we care about most: those converted from video clips and 1-3 MB in size.
- return (long) (fileSize * 0.35f);
- }
-
- public static boolean canBeTranscoded(int width, int height) {
- if (!isEnabled()) {
- return false;
- }
- return width >= MIN_WIDTH && height >= MIN_HEIGHT;
- }
-
- private static boolean isEnabled() {
- final boolean enabled = BugleGservices.get().getBoolean(
- BugleGservicesKeys.ENABLE_GIF_TRANSCODING,
- BugleGservicesKeys.ENABLE_GIF_TRANSCODING_DEFAULT);
- if (!enabled) {
- LogUtil.w(TAG, "GIF transcoding is disabled");
- }
- return enabled;
- }
-}
diff --git a/src/com/android/messaging/util/ImageUtils.java b/src/com/android/messaging/util/ImageUtils.java
deleted file mode 100644
index 05d3678..0000000
--- a/src/com/android/messaging/util/ImageUtils.java
+++ /dev/null
@@ -1,908 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.app.ActivityManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.view.View;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.exif.ExifInterface;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.io.Files;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-
-public class ImageUtils {
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final int MAX_OOM_COUNT = 1;
- private static final byte[] GIF87_HEADER = "GIF87a".getBytes(Charset.forName("US-ASCII"));
- private static final byte[] GIF89_HEADER = "GIF89a".getBytes(Charset.forName("US-ASCII"));
-
- // Used for drawBitmapWithCircleOnCanvas.
- // Default color is transparent for both circle background and stroke.
- public static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = 0;
- public static final int DEFAULT_CIRCLE_STROKE_COLOR = 0;
-
- private static volatile ImageUtils sInstance;
-
- public static ImageUtils get() {
- if (sInstance == null) {
- synchronized (ImageUtils.class) {
- if (sInstance == null) {
- sInstance = new ImageUtils();
- }
- }
- }
- return sInstance;
- }
-
- @VisibleForTesting
- public static void set(final ImageUtils imageUtils) {
- sInstance = imageUtils;
- }
-
- /**
- * Transforms a bitmap into a byte array.
- *
- * @param quality Value between 0 and 100 that the compressor uses to discern what quality the
- * resulting bytes should be
- * @param bitmap Bitmap to convert into bytes
- * @return byte array of bitmap
- */
- public static byte[] bitmapToBytes(final Bitmap bitmap, final int quality)
- throws OutOfMemoryError {
- boolean done = false;
- int oomCount = 0;
- byte[] imageBytes = null;
- while (!done) {
- try {
- final ByteArrayOutputStream os = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.JPEG, quality, os);
- imageBytes = os.toByteArray();
- done = true;
- } catch (final OutOfMemoryError e) {
- LogUtil.w(TAG, "OutOfMemory converting bitmap to bytes.");
- oomCount++;
- if (oomCount <= MAX_OOM_COUNT) {
- Factory.get().reclaimMemory();
- } else {
- done = true;
- LogUtil.w(TAG, "Failed to convert bitmap to bytes. Out of Memory.");
- }
- throw e;
- }
- }
- return imageBytes;
- }
-
- /**
- * Given the source bitmap and a canvas, draws the bitmap through a circular
- * mask. Only draws a circle with diameter equal to the destination width.
- *
- * @param bitmap The source bitmap to draw.
- * @param canvas The canvas to draw it on.
- * @param source The source bound of the bitmap.
- * @param dest The destination bound on the canvas.
- * @param bitmapPaint Optional Paint object for the bitmap
- * @param fillBackground when set, fill the circle with backgroundColor
- * @param strokeColor draw a border outside the circle with strokeColor
- */
- public static void drawBitmapWithCircleOnCanvas(final Bitmap bitmap, final Canvas canvas,
- final RectF source, final RectF dest, @Nullable Paint bitmapPaint,
- final boolean fillBackground, final int backgroundColor, int strokeColor) {
- // Draw bitmap through shader first.
- final BitmapShader shader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
- final Matrix matrix = new Matrix();
-
- // Fit bitmap to bounds.
- matrix.setRectToRect(source, dest, Matrix.ScaleToFit.CENTER);
-
- shader.setLocalMatrix(matrix);
-
- if (bitmapPaint == null) {
- bitmapPaint = new Paint();
- }
-
- bitmapPaint.setAntiAlias(true);
- if (fillBackground) {
- bitmapPaint.setColor(backgroundColor);
- canvas.drawCircle(dest.centerX(), dest.centerX(), dest.width() / 2f, bitmapPaint);
- }
-
- bitmapPaint.setShader(shader);
- canvas.drawCircle(dest.centerX(), dest.centerX(), dest.width() / 2f, bitmapPaint);
- bitmapPaint.setShader(null);
-
- if (strokeColor != 0) {
- final Paint stroke = new Paint();
- stroke.setAntiAlias(true);
- stroke.setColor(strokeColor);
- stroke.setStyle(Paint.Style.STROKE);
- final float strokeWidth = 6f;
- stroke.setStrokeWidth(strokeWidth);
- canvas.drawCircle(dest.centerX(),
- dest.centerX(),
- dest.width() / 2f - stroke.getStrokeWidth() / 2f,
- stroke);
- }
- }
-
- /**
- * Sets a drawable to the background of a view. setBackgroundDrawable() is deprecated since
- * JB and replaced by setBackground().
- */
- @SuppressWarnings("deprecation")
- public static void setBackgroundDrawableOnView(final View view, final Drawable drawable) {
- if (OsUtil.isAtLeastJB()) {
- view.setBackground(drawable);
- } else {
- view.setBackgroundDrawable(drawable);
- }
- }
-
- /**
- * Based on the input bitmap bounds given by BitmapFactory.Options, compute the required
- * sub-sampling size for loading a scaled down version of the bitmap to the required size
- * @param options a BitmapFactory.Options instance containing the bounds info of the bitmap
- * @param reqWidth the desired width of the bitmap. Can be ImageRequest.UNSPECIFIED_SIZE.
- * @param reqHeight the desired height of the bitmap. Can be ImageRequest.UNSPECIFIED_SIZE.
- * @return
- */
- public int calculateInSampleSize(
- final BitmapFactory.Options options, final int reqWidth, final int reqHeight) {
- // Raw height and width of image
- final int height = options.outHeight;
- final int width = options.outWidth;
- int inSampleSize = 1;
-
- final boolean checkHeight = reqHeight != ImageRequest.UNSPECIFIED_SIZE;
- final boolean checkWidth = reqWidth != ImageRequest.UNSPECIFIED_SIZE;
- if ((checkHeight && height > reqHeight) ||
- (checkWidth && width > reqWidth)) {
-
- final int halfHeight = height / 2;
- final int halfWidth = width / 2;
-
- // Calculate the largest inSampleSize value that is a power of 2 and keeps both
- // height and width larger than the requested height and width.
- while ((!checkHeight || (halfHeight / inSampleSize) > reqHeight)
- && (!checkWidth || (halfWidth / inSampleSize) > reqWidth)) {
- inSampleSize *= 2;
- }
- }
-
- return inSampleSize;
- }
-
- private static final String[] MEDIA_CONTENT_PROJECTION = new String[] {
- MediaStore.MediaColumns.MIME_TYPE
- };
-
- private static final int INDEX_CONTENT_TYPE = 0;
-
- @DoesNotRunOnMainThread
- public static String getContentType(final ContentResolver cr, final Uri uri) {
- // Figure out the content type of media.
- String contentType = null;
- Cursor cursor = null;
- if (UriUtil.isMediaStoreUri(uri)) {
- try {
- cursor = cr.query(uri, MEDIA_CONTENT_PROJECTION, null, null, null);
-
- if (cursor != null && cursor.moveToFirst()) {
- contentType = cursor.getString(INDEX_CONTENT_TYPE);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- if (contentType == null) {
- // Last ditch effort to get the content type. Look at the file extension.
- contentType = ContentType.getContentTypeFromExtension(uri.toString(),
- ContentType.IMAGE_UNSPECIFIED);
- }
- return contentType;
- }
-
- /**
- * @param context Android context
- * @param uri Uri to the image data
- * @return The exif orientation value for the image in the specified uri
- */
- public static int getOrientation(final Context context, final Uri uri) {
- try {
- return getOrientation(context.getContentResolver().openInputStream(uri));
- } catch (FileNotFoundException e) {
- LogUtil.e(TAG, "getOrientation couldn't open: " + uri, e);
- }
- return android.media.ExifInterface.ORIENTATION_UNDEFINED;
- }
-
- /**
- * @param inputStream The stream to the image file. Closed on completion
- * @return The exif orientation value for the image in the specified stream
- */
- public static int getOrientation(final InputStream inputStream) {
- int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED;
- if (inputStream != null) {
- try {
- final ExifInterface exifInterface = new ExifInterface();
- exifInterface.readExif(inputStream);
- final Integer orientationValue =
- exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION);
- if (orientationValue != null) {
- orientation = orientationValue.intValue();
- }
- } catch (IOException e) {
- // If the image if GIF, PNG, or missing exif header, just use the defaults
- } finally {
- try {
- if (inputStream != null) {
- inputStream.close();
- }
- } catch (IOException e) {
- LogUtil.e(TAG, "getOrientation error closing input stream", e);
- }
- }
- }
- return orientation;
- }
-
- /**
- * Returns whether the resource is a GIF image.
- */
- public static boolean isGif(String contentType, Uri contentUri) {
- if (TextUtils.equals(contentType, ContentType.IMAGE_GIF)) {
- return true;
- }
- if (ContentType.isImageType(contentType)) {
- try {
- ContentResolver contentResolver = Factory.get().getApplicationContext()
- .getContentResolver();
- InputStream inputStream = contentResolver.openInputStream(contentUri);
- return ImageUtils.isGif(inputStream);
- } catch (Exception e) {
- LogUtil.w(TAG, "Could not open GIF input stream", e);
- }
- }
- // Assume anything with a non-image content type is not a GIF
- return false;
- }
-
- /**
- * @param inputStream The stream to the image file. Closed on completion
- * @return Whether the image stream represents a GIF
- */
- public static boolean isGif(InputStream inputStream) {
- if (inputStream != null) {
- try {
- byte[] gifHeaderBytes = new byte[6];
- int value = inputStream.read(gifHeaderBytes, 0, 6);
- if (value == 6) {
- return Arrays.equals(gifHeaderBytes, GIF87_HEADER)
- || Arrays.equals(gifHeaderBytes, GIF89_HEADER);
- }
- } catch (IOException e) {
- return false;
- } finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- return false;
- }
-
- /**
- * Read an image and compress it to particular max dimensions and size.
- * Used to ensure images can fit in an MMS.
- * TODO: This uses memory very inefficiently as it processes the whole image as a unit
- * (rather than slice by slice) but system JPEG functions do not support slicing and dicing.
- */
- public static class ImageResizer {
-
- /**
- * The quality parameter which is used to compress JPEG images.
- */
- private static final int IMAGE_COMPRESSION_QUALITY = 95;
- /**
- * The minimum quality parameter which is used to compress JPEG images.
- */
- private static final int MINIMUM_IMAGE_COMPRESSION_QUALITY = 50;
-
- /**
- * Minimum factor to reduce quality value
- */
- private static final double QUALITY_SCALE_DOWN_RATIO = 0.85f;
-
- /**
- * Maximum passes through the resize loop before failing permanently
- */
- private static final int NUMBER_OF_RESIZE_ATTEMPTS = 6;
-
- /**
- * Amount to scale down the picture when it doesn't fit
- */
- private static final float MIN_SCALE_DOWN_RATIO = 0.75f;
-
- /**
- * When computing sampleSize target scaling of no more than this ratio
- */
- private static final float MAX_TARGET_SCALE_FACTOR = 1.5f;
-
-
- // Current sample size for subsampling image during initial decode
- private int mSampleSize;
- // Current bitmap holding initial decoded source image
- private Bitmap mDecoded;
- // If scaling is needed this holds the scaled bitmap (else should equal mDecoded)
- private Bitmap mScaled;
- // Current JPEG compression quality to use when compressing image
- private int mQuality;
- // Current factor to scale down decoded image before compressing
- private float mScaleFactor;
- // Flag keeping track of whether cache memory has been reclaimed
- private boolean mHasReclaimedMemory;
-
- // Initial size of the image (typically provided but can be UNSPECIFIED_SIZE)
- private int mWidth;
- private int mHeight;
- // Orientation params of image as read from EXIF data
- private final ExifInterface.OrientationParams mOrientationParams;
- // Matrix to undo orientation and scale at the same time
- private final Matrix mMatrix;
- // Size limit as provided by MMS library
- private final int mWidthLimit;
- private final int mHeightLimit;
- private final int mByteLimit;
- // Uri from which to read source image
- private final Uri mUri;
- // Application context
- private final Context mContext;
- // Cached value of bitmap factory options
- private final BitmapFactory.Options mOptions;
- private final String mContentType;
-
- private final int mMemoryClass;
-
- /**
- * Return resized (compressed) image (else null)
- *
- * @param width The width of the image (if known)
- * @param height The height of the image (if known)
- * @param orientation The orientation of the image as an ExifInterface constant
- * @param widthLimit The width limit, in pixels
- * @param heightLimit The height limit, in pixels
- * @param byteLimit The binary size limit, in bytes
- * @param uri Uri to the image data
- * @param context Needed to open the image
- * @param contentType of image
- * @return encoded image meeting size requirements else null
- */
- public static byte[] getResizedImageData(final int width, final int height,
- final int orientation, final int widthLimit, final int heightLimit,
- final int byteLimit, final Uri uri, final Context context,
- final String contentType) {
- final ImageResizer resizer = new ImageResizer(width, height, orientation,
- widthLimit, heightLimit, byteLimit, uri, context, contentType);
- return resizer.resize();
- }
-
- /**
- * Create and initialize an image resizer
- */
- private ImageResizer(final int width, final int height, final int orientation,
- final int widthLimit, final int heightLimit, final int byteLimit, final Uri uri,
- final Context context, final String contentType) {
- mWidth = width;
- mHeight = height;
- mOrientationParams = ExifInterface.getOrientationParams(orientation);
- mMatrix = new Matrix();
- mWidthLimit = widthLimit;
- mHeightLimit = heightLimit;
- mByteLimit = byteLimit;
- mUri = uri;
- mWidth = width;
- mContext = context;
- mQuality = IMAGE_COMPRESSION_QUALITY;
- mScaleFactor = 1.0f;
- mHasReclaimedMemory = false;
- mOptions = new BitmapFactory.Options();
- mOptions.inScaled = false;
- mOptions.inDensity = 0;
- mOptions.inTargetDensity = 0;
- mOptions.inSampleSize = 1;
- mOptions.inJustDecodeBounds = false;
- mOptions.inMutable = false;
- final ActivityManager am =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mMemoryClass = Math.max(16, am.getMemoryClass());
- mContentType = contentType;
- }
-
- /**
- * Try to compress the image
- *
- * @return encoded image meeting size requirements else null
- */
- private byte[] resize() {
- return ImageUtils.isGif(mContentType, mUri) ? resizeGifImage() : resizeStaticImage();
- }
-
- private byte[] resizeGifImage() {
- byte[] bytesToReturn = null;
- final String inputFilePath;
- if (MediaScratchFileProvider.isMediaScratchSpaceUri(mUri)) {
- inputFilePath = MediaScratchFileProvider.getFileFromUri(mUri).getAbsolutePath();
- } else {
- if (!TextUtils.equals(mUri.getScheme(), ContentResolver.SCHEME_FILE)) {
- Assert.fail("Expected a GIF file uri, but actual uri = " + mUri.toString());
- }
- inputFilePath = mUri.getPath();
- }
-
- if (GifTranscoder.canBeTranscoded(mWidth, mHeight)) {
- // Needed to perform the transcoding so that the gif can continue to play in the
- // conversation while the sending is taking place
- final Uri tmpUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("gif");
- final File outputFile = MediaScratchFileProvider.getFileFromUri(tmpUri);
- final String outputFilePath = outputFile.getAbsolutePath();
-
- final boolean success =
- GifTranscoder.transcode(mContext, inputFilePath, outputFilePath);
- if (success) {
- try {
- bytesToReturn = Files.toByteArray(outputFile);
- } catch (IOException e) {
- LogUtil.e(TAG, "Could not create FileInputStream with path of "
- + outputFilePath, e);
- }
- }
-
- // Need to clean up the new file created to compress the gif
- mContext.getContentResolver().delete(tmpUri, null, null);
- } else {
- // We don't want to transcode the gif because its image dimensions would be too
- // small so just return the bytes of the original gif
- try {
- bytesToReturn = Files.toByteArray(new File(inputFilePath));
- } catch (IOException e) {
- LogUtil.e(TAG,
- "Could not create FileInputStream with path of " + inputFilePath, e);
- }
- }
-
- return bytesToReturn;
- }
-
- private byte[] resizeStaticImage() {
- if (!ensureImageSizeSet()) {
- // Cannot read image size
- return null;
- }
- // Find incoming image size
- if (!canBeCompressed()) {
- return null;
- }
-
- // Decode image - if out of memory - reclaim memory and retry
- try {
- for (int attempts = 0; attempts < NUMBER_OF_RESIZE_ATTEMPTS; attempts++) {
- final byte[] encoded = recodeImage(attempts);
-
- // Only return data within the limit
- if (encoded != null && encoded.length <= mByteLimit) {
- return encoded;
- } else {
- final int currentSize = (encoded == null ? 0 : encoded.length);
- updateRecodeParameters(currentSize);
- }
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "File disappeared during resizing");
- } finally {
- // Release all bitmaps
- if (mScaled != null && mScaled != mDecoded) {
- mScaled.recycle();
- }
- if (mDecoded != null) {
- mDecoded.recycle();
- }
- }
- return null;
- }
-
- /**
- * Ensure that the width and height of the source image are known
- * @return flag indicating whether size is known
- */
- private boolean ensureImageSizeSet() {
- if (mWidth == MessagingContentProvider.UNSPECIFIED_SIZE ||
- mHeight == MessagingContentProvider.UNSPECIFIED_SIZE) {
- // First get the image data (compressed)
- final ContentResolver cr = mContext.getContentResolver();
- InputStream inputStream = null;
- // Find incoming image size
- try {
- mOptions.inJustDecodeBounds = true;
- inputStream = cr.openInputStream(mUri);
- BitmapFactory.decodeStream(inputStream, null, mOptions);
-
- mWidth = mOptions.outWidth;
- mHeight = mOptions.outHeight;
- mOptions.inJustDecodeBounds = false;
-
- return true;
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "Could not open file corresponding to uri " + mUri, e);
- } catch (final NullPointerException e) {
- LogUtil.e(TAG, "NPE trying to open the uri " + mUri, e);
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (final IOException e) {
- // Nothing to do
- }
- }
- }
-
- return false;
- }
- return true;
- }
-
- /**
- * Choose an initial subsamplesize that ensures the decoded image is no more than
- * MAX_TARGET_SCALE_FACTOR bigger than largest supported image and that it is likely to
- * compress to smaller than the target size (assuming compression down to 1 bit per pixel).
- * @return whether the image can be down subsampled
- */
- private boolean canBeCompressed() {
- final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE);
-
- int imageHeight = mHeight;
- int imageWidth = mWidth;
-
- // Assume can use half working memory to decode the initial image (4 bytes per pixel)
- final int workingMemoryPixelLimit = (mMemoryClass * 1024 * 1024 / 8);
- // Target 1 bits per pixel in final compressed image
- final int finalSizePixelLimit = mByteLimit * 8;
- // When choosing to halve the resolution - only do so the image will still be too big
- // after scaling by MAX_TARGET_SCALE_FACTOR
- final int heightLimitWithSlop = (int) (mHeightLimit * MAX_TARGET_SCALE_FACTOR);
- final int widthLimitWithSlop = (int) (mWidthLimit * MAX_TARGET_SCALE_FACTOR);
- final int pixelLimitWithSlop = (int) (finalSizePixelLimit *
- MAX_TARGET_SCALE_FACTOR * MAX_TARGET_SCALE_FACTOR);
- final int pixelLimit = Math.min(pixelLimitWithSlop, workingMemoryPixelLimit);
-
- int sampleSize = 1;
- boolean fits = (imageHeight < heightLimitWithSlop &&
- imageWidth < widthLimitWithSlop &&
- imageHeight * imageWidth < pixelLimit);
-
- // Compare sizes to compute sub-sampling needed
- while (!fits) {
- sampleSize = sampleSize * 2;
- // Note that recodeImage may try using mSampleSize * 2. Hence we use the factor of 4
- if (sampleSize >= (Integer.MAX_VALUE / 4)) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, String.format(
- "Cannot resize image: widthLimit=%d heightLimit=%d byteLimit=%d " +
- "imageWidth=%d imageHeight=%d", mWidthLimit, mHeightLimit, mByteLimit,
- mWidth, mHeight));
- Assert.fail("Image cannot be resized"); // http://b/18926934
- return false;
- }
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "computeInitialSampleSize: Increasing sampleSize to " + sampleSize
- + " as h=" + imageHeight + " vs " + heightLimitWithSlop
- + " w=" + imageWidth + " vs " + widthLimitWithSlop
- + " p=" + imageHeight * imageWidth + " vs " + pixelLimit);
- }
- imageHeight = mHeight / sampleSize;
- imageWidth = mWidth / sampleSize;
- fits = (imageHeight < heightLimitWithSlop &&
- imageWidth < widthLimitWithSlop &&
- imageHeight * imageWidth < pixelLimit);
- }
-
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "computeInitialSampleSize: Initial sampleSize " + sampleSize
- + " for h=" + imageHeight + " vs " + heightLimitWithSlop
- + " w=" + imageWidth + " vs " + widthLimitWithSlop
- + " p=" + imageHeight * imageWidth + " vs " + pixelLimit);
- }
-
- mSampleSize = sampleSize;
- return true;
- }
-
- /**
- * Recode the image from initial Uri to encoded JPEG
- * @param attempt Attempt number
- * @return encoded image
- */
- private byte[] recodeImage(final int attempt) throws FileNotFoundException {
- byte[] encoded = null;
- try {
- final ContentResolver cr = mContext.getContentResolver();
- final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE);
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: attempt=" + attempt
- + " limit (w=" + mWidthLimit + " h=" + mHeightLimit + ") quality="
- + mQuality + " scale=" + mScaleFactor + " sampleSize=" + mSampleSize);
- }
- if (mScaled == null) {
- if (mDecoded == null) {
- mOptions.inSampleSize = mSampleSize;
- final InputStream inputStream = cr.openInputStream(mUri);
- mDecoded = BitmapFactory.decodeStream(inputStream, null, mOptions);
- if (mDecoded == null) {
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "getResizedImageData: got empty decoded bitmap");
- }
- return null;
- }
- }
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: decoded w,h="
- + mDecoded.getWidth() + "," + mDecoded.getHeight());
- }
- // Make sure to scale the decoded image if dimension is not within limit
- final int decodedWidth = mDecoded.getWidth();
- final int decodedHeight = mDecoded.getHeight();
- if (decodedWidth > mWidthLimit || decodedHeight > mHeightLimit) {
- final float minScaleFactor = Math.max(
- mWidthLimit == 0 ? 1.0f :
- (float) decodedWidth / (float) mWidthLimit,
- mHeightLimit == 0 ? 1.0f :
- (float) decodedHeight / (float) mHeightLimit);
- if (mScaleFactor < minScaleFactor) {
- mScaleFactor = minScaleFactor;
- }
- }
- if (mScaleFactor > 1.0 || mOrientationParams.rotation != 0) {
- mMatrix.reset();
- mMatrix.postRotate(mOrientationParams.rotation);
- mMatrix.postScale(mOrientationParams.scaleX / mScaleFactor,
- mOrientationParams.scaleY / mScaleFactor);
- mScaled = Bitmap.createBitmap(mDecoded, 0, 0, decodedWidth, decodedHeight,
- mMatrix, false /* filter */);
- if (mScaled == null) {
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "getResizedImageData: got empty scaled bitmap");
- }
- return null;
- }
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: scaled w,h="
- + mScaled.getWidth() + "," + mScaled.getHeight());
- }
- } else {
- mScaled = mDecoded;
- }
- }
- // Now encode it at current quality
- encoded = ImageUtils.bitmapToBytes(mScaled, mQuality);
- if (encoded != null && logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "getResizedImageData: Encoded down to " + encoded.length + "@"
- + mScaled.getWidth() + "/" + mScaled.getHeight() + "~"
- + mQuality);
- }
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG,
- "getResizedImageData - image too big (OutOfMemoryError), will try "
- + " with smaller scale factor");
- // fall through and keep trying with more compression
- }
- return encoded;
- }
-
- /**
- * When image recode fails this method updates compression parameters for the next attempt
- * @param currentSize encoded image size (will be 0 if OOM)
- */
- private void updateRecodeParameters(final int currentSize) {
- final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE);
- // Only return data within the limit
- if (currentSize > 0 &&
- mQuality > MINIMUM_IMAGE_COMPRESSION_QUALITY) {
- // First if everything succeeded but failed to hit target size
- // Try quality proportioned to sqrt of size over size limit
- mQuality = Math.max(MINIMUM_IMAGE_COMPRESSION_QUALITY,
- Math.min((int) (mQuality * Math.sqrt((1.0 * mByteLimit) / currentSize)),
- (int) (mQuality * QUALITY_SCALE_DOWN_RATIO)));
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "getResizedImageData: Retrying at quality " + mQuality);
- }
- } else if (currentSize > 0 &&
- mScaleFactor < 2.0 * MIN_SCALE_DOWN_RATIO * MIN_SCALE_DOWN_RATIO) {
- // JPEG compression failed to hit target size - need smaller image
- // First try scaling by a little (< factor of 2) just so long resulting scale down
- // ratio is still significantly bigger than next subsampling step
- // i.e. mScaleFactor/MIN_SCALE_DOWN_RATIO (new scaling factor) <
- // 2.0 / MIN_SCALE_DOWN_RATIO (arbitrary limit)
- mQuality = IMAGE_COMPRESSION_QUALITY;
- mScaleFactor = mScaleFactor / MIN_SCALE_DOWN_RATIO;
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "getResizedImageData: Retrying at scale " + mScaleFactor);
- }
- // Release scaled bitmap to trigger rescaling
- if (mScaled != null && mScaled != mDecoded) {
- mScaled.recycle();
- }
- mScaled = null;
- } else if (currentSize <= 0 && !mHasReclaimedMemory) {
- // Then before we subsample try cleaning up our cached memory
- Factory.get().reclaimMemory();
- mHasReclaimedMemory = true;
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "getResizedImageData: Retrying after reclaiming memory ");
- }
- } else {
- // Last resort - subsample image by another factor of 2 and try again
- mSampleSize = mSampleSize * 2;
- mQuality = IMAGE_COMPRESSION_QUALITY;
- mScaleFactor = 1.0f;
- if (logv) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
- "getResizedImageData: Retrying at sampleSize " + mSampleSize);
- }
- // Release all bitmaps to trigger subsampling
- if (mScaled != null && mScaled != mDecoded) {
- mScaled.recycle();
- }
- mScaled = null;
- if (mDecoded != null) {
- mDecoded.recycle();
- mDecoded = null;
- }
- }
- }
- }
-
- /**
- * Scales and center-crops a bitmap to the size passed in and returns the new bitmap.
- *
- * @param source Bitmap to scale and center-crop
- * @param newWidth destination width
- * @param newHeight destination height
- * @return Bitmap scaled and center-cropped bitmap
- */
- public static Bitmap scaleCenterCrop(final Bitmap source, final int newWidth,
- final int newHeight) {
- final int sourceWidth = source.getWidth();
- final int sourceHeight = source.getHeight();
-
- // Compute the scaling factors to fit the new height and width, respectively.
- // To cover the final image, the final scaling will be the bigger
- // of these two.
- final float xScale = (float) newWidth / sourceWidth;
- final float yScale = (float) newHeight / sourceHeight;
- final float scale = Math.max(xScale, yScale);
-
- // Now get the size of the source bitmap when scaled
- final float scaledWidth = scale * sourceWidth;
- final float scaledHeight = scale * sourceHeight;
-
- // Let's find out the upper left coordinates if the scaled bitmap
- // should be centered in the new size give by the parameters
- final float left = (newWidth - scaledWidth) / 2;
- final float top = (newHeight - scaledHeight) / 2;
-
- // The target rectangle for the new, scaled version of the source bitmap will now
- // be
- final RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
-
- // Finally, we create a new bitmap of the specified size and draw our new,
- // scaled bitmap onto it.
- final Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
- final Canvas canvas = new Canvas(dest);
- canvas.drawBitmap(source, null, targetRect, null);
-
- return dest;
- }
-
- /**
- * The drawable can be a Nine-Patch. If we directly use the same drawable instance for each
- * drawable of different sizes, then the drawable sizes would interfere with each other. The
- * solution here is to create a new drawable instance for every time with the SAME
- * ConstantState (i.e. sharing the same common state such as the bitmap, so that we don't have
- * to recreate the bitmap resource), and apply the different properties on top (nine-patch
- * size and color tint).
- *
- * TODO: we are creating new drawable instances here, but there are optimizations that
- * can be made. For example, message bubbles shouldn't need the mutate() call and the
- * play/pause buttons shouldn't need to create new drawable from the constant state.
- */
- public static Drawable getTintedDrawable(final Context context, final Drawable drawable,
- final int color) {
- // For some reason occassionally drawables on JB has a null constant state
- final Drawable.ConstantState constantStateDrawable = drawable.getConstantState();
- final Drawable retDrawable = (constantStateDrawable != null)
- ? constantStateDrawable.newDrawable(context.getResources()).mutate()
- : drawable;
- retDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
- return retDrawable;
- }
-
- /**
- * Decodes image resource header and returns the image size.
- */
- public static Rect decodeImageBounds(final Context context, final Uri imageUri) {
- final ContentResolver cr = context.getContentResolver();
- try {
- final InputStream inputStream = cr.openInputStream(imageUri);
- if (inputStream != null) {
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(inputStream, null, options);
- return new Rect(0, 0, options.outWidth, options.outHeight);
- } finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- // Do nothing.
- }
- }
- }
- } catch (FileNotFoundException e) {
- LogUtil.e(TAG, "Couldn't open input stream for uri = " + imageUri);
- }
- return new Rect(0, 0, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE);
- }
-}
diff --git a/src/com/android/messaging/util/ImeUtil.java b/src/com/android/messaging/util/ImeUtil.java
deleted file mode 100644
index ab0df13..0000000
--- a/src/com/android/messaging/util/ImeUtil.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-
-import com.google.common.annotations.VisibleForTesting;
-
-public class ImeUtil {
- public interface ImeStateObserver {
- void onImeStateChanged(boolean imeOpen);
- }
-
- public interface ImeStateHost {
- void onDisplayHeightChanged(int heightMeasureSpec);
- void registerImeStateObserver(ImeUtil.ImeStateObserver observer);
- void unregisterImeStateObserver(ImeUtil.ImeStateObserver observer);
- boolean isImeOpen();
- }
-
- private static volatile ImeUtil sInstance;
-
- // Used to clear the static cached instance of ImeUtil during testing. This is necessary
- // because a previous test may have installed a mocked instance (or vice versa).
- public static void clearInstance() {
- sInstance = null;
- }
- public static ImeUtil get() {
- if (sInstance == null) {
- synchronized (ImeUtil.class) {
- if (sInstance == null) {
- sInstance = new ImeUtil();
- }
- }
- }
- return sInstance;
- }
-
- @VisibleForTesting
- public static void set(final ImeUtil imeUtil) {
- sInstance = imeUtil;
- }
-
- public void hideImeKeyboard(@NonNull final Context context, @NonNull final View v) {
- Assert.notNull(context);
- Assert.notNull(v);
-
- final InputMethodManager inputMethodManager =
- (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- if (inputMethodManager != null) {
- inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0 /* flags */);
- }
- }
-
- public void showImeKeyboard(@NonNull final Context context, @NonNull final View v) {
- Assert.notNull(context);
- Assert.notNull(v);
-
- final InputMethodManager inputMethodManager =
- (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- if (inputMethodManager != null) {
- v.requestFocus();
- inputMethodManager.showSoftInput(v, 0 /* flags */);
- }
- }
-
- public static void hideSoftInput(@NonNull final Context context, @NonNull final View v) {
- final InputMethodManager inputMethodManager =
- (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
- }
-}
diff --git a/src/com/android/messaging/util/LogSaver.java b/src/com/android/messaging/util/LogSaver.java
deleted file mode 100644
index 7d1f2fd..0000000
--- a/src/com/android/messaging/util/LogSaver.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.os.Process;
-import android.util.Log;
-
-import com.android.messaging.Factory;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.logging.FileHandler;
-import java.util.logging.Formatter;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Save the app's own log to dump along with adb bugreport
- */
-public abstract class LogSaver {
- /**
- * Writes the accumulated log entries, from oldest to newest, to the specified PrintWriter.
- * Log lines are emitted in much the same form as logcat -v threadtime -- specifically,
- * lines will include a timestamp, pid, tid, level, and tag.
- *
- * @param writer The PrintWriter to output
- */
- public abstract void dump(PrintWriter writer);
-
- /**
- * Log a line
- *
- * @param level The log level to use
- * @param tag The log tag
- * @param msg The message of the log line
- */
- public abstract void log(int level, String tag, String msg);
-
- /**
- * Check if the LogSaver still matches the current Gservices settings
- *
- * @return true if matches, false otherwise
- */
- public abstract boolean isCurrent();
-
- private LogSaver() {
- }
-
- public static LogSaver newInstance() {
- final boolean persistent = BugleGservices.get().getBoolean(
- BugleGservicesKeys.PERSISTENT_LOGSAVER,
- BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
- if (persistent) {
- final int setSize = BugleGservices.get().getInt(
- BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE,
- BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT);
- final int fileLimitBytes = BugleGservices.get().getInt(
- BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES,
- BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT);
- return new DiskLogSaver(setSize, fileLimitBytes);
- } else {
- final int size = BugleGservices.get().getInt(
- BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT,
- BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT);
- return new MemoryLogSaver(size);
- }
- }
-
- /**
- * A circular in-memory log to be used to log potentially verbose logs. The logs will be
- * persisted in memory in the application and can be dumped by various dump() methods.
- * For example, adb shell dumpsys activity provider com.android.messaging.
- * The dump will also show up in bugreports.
- */
- private static final class MemoryLogSaver extends LogSaver {
- /**
- * Record to store a single log entry. Stores timestamp, tid, level, tag, and message.
- * It can be reused when the circular log rolls over. This avoids creating new objects.
- */
- private static class LogRecord {
- int mTid;
- String mLevelString;
- long mTimeMillis; // from System.currentTimeMillis
- String mTag;
- String mMessage;
-
- LogRecord() {
- }
-
- void set(int tid, int level, long time, String tag, String message) {
- this.mTid = tid;
- this.mTimeMillis = time;
- this.mTag = tag;
- this.mMessage = message;
- this.mLevelString = getLevelString(level);
- }
- }
-
- private final int mSize;
- private final CircularArray<LogRecord> mLogList;
- private final Object mLock;
-
- private final SimpleDateFormat mSdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-
- public MemoryLogSaver(final int size) {
- mSize = size;
- mLogList = new CircularArray<LogRecord>(size);
- mLock = new Object();
- }
-
- @Override
- public void dump(PrintWriter writer) {
- int pid = Process.myPid();
- synchronized (mLock) {
- for (int i = 0; i < mLogList.count(); i++) {
- LogRecord rec = mLogList.get(i);
- writer.println(String.format("%s %5d %5d %s %s: %s",
- mSdf.format(rec.mTimeMillis),
- pid, rec.mTid, rec.mLevelString, rec.mTag, rec.mMessage));
- }
- }
- }
-
- @Override
- public void log(int level, String tag, String msg) {
- synchronized (mLock) {
- LogRecord rec = mLogList.getFree();
- if (rec == null) {
- rec = new LogRecord();
- }
- rec.set(Process.myTid(), level, System.currentTimeMillis(), tag, msg);
- mLogList.add(rec);
- }
- }
-
- @Override
- public boolean isCurrent() {
- final boolean persistent = BugleGservices.get().getBoolean(
- BugleGservicesKeys.PERSISTENT_LOGSAVER,
- BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
- if (persistent) {
- return false;
- }
- final int size = BugleGservices.get().getInt(
- BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT,
- BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT);
- return size == mSize;
- }
- }
-
- /**
- * A persistent, on-disk log saver. It uses the standard Java util logger along with
- * a rotation log file set to store the logs in app's local file directory "app_logs".
- */
- private static final class DiskLogSaver extends LogSaver {
- private static final String DISK_LOG_DIR_NAME = "logs";
-
- private final int mSetSize;
- private final int mFileLimitBytes;
- private Logger mDiskLogger;
-
- public DiskLogSaver(final int setSize, final int fileLimitBytes) {
- Assert.isTrue(setSize > 0);
- Assert.isTrue(fileLimitBytes > 0);
- mSetSize = setSize;
- mFileLimitBytes = fileLimitBytes;
- initDiskLog();
- }
-
- private static void clearDefaultHandlers(Logger logger) {
- Assert.notNull(logger);
- for (Handler handler : logger.getHandlers()) {
- logger.removeHandler(handler);
- }
- }
-
- private void initDiskLog() {
- mDiskLogger = Logger.getLogger(LogUtil.BUGLE_TAG);
- // We don't want the default console handler
- clearDefaultHandlers(mDiskLogger);
- // Don't want duplicate print in system log
- mDiskLogger.setUseParentHandlers(false);
- // FileHandler manages the log files in a fixed rotation set
- final File logDir = Factory.get().getApplicationContext().getDir(
- DISK_LOG_DIR_NAME, 0/*mode*/);
- FileHandler handler = null;
- try {
- handler = new FileHandler(
- logDir + "/%g.log", mFileLimitBytes, mSetSize, true/*append*/);
- } catch (Exception e) {
- Log.e(LogUtil.BUGLE_TAG, "LogSaver: fail to init disk logger", e);
- return;
- }
- final Formatter formatter = new Formatter() {
- @Override
- public String format(java.util.logging.LogRecord r) {
- return r.getMessage();
- }
- };
- handler.setFormatter(formatter);
- handler.setLevel(Level.ALL);
- mDiskLogger.addHandler(handler);
- }
-
- @Override
- public void dump(PrintWriter writer) {
- for (int i = mSetSize - 1; i >= 0; i--) {
- final File logDir = Factory.get().getApplicationContext().getDir(
- DISK_LOG_DIR_NAME, 0/*mode*/);
- final String logFilePath = logDir + "/" + i + ".log";
- try {
- final File logFile = new File(logFilePath);
- if (!logFile.exists()) {
- continue;
- }
- final BufferedReader reader = new BufferedReader(new FileReader(logFile));
- for (String line; (line = reader.readLine()) != null;) {
- line = line.trim();
- writer.println(line);
- }
- } catch (FileNotFoundException e) {
- Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not find log file " + logFilePath);
- } catch (IOException e) {
- Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not read log file", e);
- }
- }
- }
-
- @Override
- public void log(int level, String tag, String msg) {
- final SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
- mDiskLogger.info(String.format("%s %5d %5d %s %s: %s\n",
- sdf.format(System.currentTimeMillis()),
- Process.myPid(), Process.myTid(), getLevelString(level), tag, msg));
- }
-
- @Override
- public boolean isCurrent() {
- final boolean persistent = BugleGservices.get().getBoolean(
- BugleGservicesKeys.PERSISTENT_LOGSAVER,
- BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
- if (!persistent) {
- return false;
- }
- final int setSize = BugleGservices.get().getInt(
- BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE,
- BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT);
- final int fileLimitBytes = BugleGservices.get().getInt(
- BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES,
- BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT);
- return setSize == mSetSize && fileLimitBytes == mFileLimitBytes;
- }
- }
-
- private static String getLevelString(final int level) {
- switch (level) {
- case android.util.Log.DEBUG:
- return "D";
- case android.util.Log.WARN:
- return "W";
- case android.util.Log.INFO:
- return "I";
- case android.util.Log.VERBOSE:
- return "V";
- case android.util.Log.ERROR:
- return "E";
- case android.util.Log.ASSERT:
- return "A";
- default:
- return "?";
- }
- }
-}
diff --git a/src/com/android/messaging/util/LogUtil.java b/src/com/android/messaging/util/LogUtil.java
deleted file mode 100644
index 021f39b..0000000
--- a/src/com/android/messaging/util/LogUtil.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-/**
- * Log utility class.
- */
-public class LogUtil {
- public static final String BUGLE_TAG = "MessagingApp";
- public static final String PROFILE_TAG = "MessagingAppProf";
- public static final String BUGLE_DATABASE_TAG = "MessagingAppDb";
- public static final String BUGLE_DATABASE_PERF_TAG = "MessagingAppDbPerf";
- public static final String BUGLE_DATAMODEL_TAG = "MessagingAppDataModel";
- public static final String BUGLE_IMAGE_TAG = "MessagingAppImage";
- public static final String BUGLE_NOTIFICATIONS_TAG = "MessagingAppNotif";
- public static final String BUGLE_WIDGET_TAG = "MessagingAppWidget";
-
- public static final int DEBUG = android.util.Log.DEBUG;
- public static final int WARN = android.util.Log.WARN;
- public static final int VERBOSE = android.util.Log.VERBOSE;
- public static final int INFO = android.util.Log.INFO;
- public static final int ERROR = android.util.Log.ERROR;
-
- // If this is non-null, DEBUG and higher logs will be tracked in-memory. It will not include
- // VERBOSE logs.
- private static LogSaver sDebugLogSaver;
- private static volatile boolean sCaptureDebugLogs;
-
- /**
- * Read Gservices to see if logging should be enabled.
- */
- public static void refreshGservices(final BugleGservices gservices) {
- sCaptureDebugLogs = gservices.getBoolean(
- BugleGservicesKeys.ENABLE_LOG_SAVER,
- BugleGservicesKeys.ENABLE_LOG_SAVER_DEFAULT);
- if (sCaptureDebugLogs && (sDebugLogSaver == null || !sDebugLogSaver.isCurrent())) {
- // We were not capturing logs before. We are now.
- sDebugLogSaver = LogSaver.newInstance();
- } else if (!sCaptureDebugLogs && sDebugLogSaver != null) {
- // We were capturing logs. We aren't anymore.
- sDebugLogSaver = null;
- }
- }
-
- // This is called from FactoryImpl once the Gservices class is initialized.
- public static void initializeGservices (final BugleGservices gservices) {
- gservices.registerForChanges(new Runnable() {
- @Override
- public void run() {
- refreshGservices(gservices);
- }
- });
- refreshGservices(gservices);
- }
-
- /**
- * Send a {@link #VERBOSE} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- */
- public static void v(final String tag, final String msg) {
- println(android.util.Log.VERBOSE, tag, msg);
- }
-
- /**
- * Send a {@link #VERBOSE} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- * @param tr An exception to log
- */
- public static void v(final String tag, final String msg, final Throwable tr) {
- println(android.util.Log.VERBOSE, tag, msg + '\n'
- + android.util.Log.getStackTraceString(tr));
- }
-
- /**
- * Send a {@link #DEBUG} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- */
- public static void d(final String tag, final String msg) {
- println(android.util.Log.DEBUG, tag, msg);
- }
-
- /**
- * Send a {@link #DEBUG} log message and log the exception.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- * @param tr An exception to log
- */
- public static void d(final String tag, final String msg, final Throwable tr) {
- println(android.util.Log.DEBUG, tag, msg + '\n'
- + android.util.Log.getStackTraceString(tr));
- }
-
- /**
- * Send an {@link #INFO} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- */
- public static void i(final String tag, final String msg) {
- println(android.util.Log.INFO, tag, msg);
- }
-
- /**
- * Send a {@link #INFO} log message and log the exception.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- * @param tr An exception to log
- */
- public static void i(final String tag, final String msg, final Throwable tr) {
- println(android.util.Log.INFO, tag, msg + '\n'
- + android.util.Log.getStackTraceString(tr));
- }
-
- /**
- * Send a {@link #WARN} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- */
- public static void w(final String tag, final String msg) {
- println(android.util.Log.WARN, tag, msg);
- }
-
- /**
- * Send a {@link #WARN} log message and log the exception.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- * @param tr An exception to log
- */
- public static void w(final String tag, final String msg, final Throwable tr) {
- println(android.util.Log.WARN, tag, msg);
- println(android.util.Log.WARN, tag, android.util.Log.getStackTraceString(tr));
- }
-
- /**
- * Send an {@link #ERROR} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- */
- public static void e(final String tag, final String msg) {
- println(android.util.Log.ERROR, tag, msg);
- }
-
- /**
- * Send a {@link #ERROR} log message and log the exception.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- * @param tr An exception to log
- */
- public static void e(final String tag, final String msg, final Throwable tr) {
- println(android.util.Log.ERROR, tag, msg);
- println(android.util.Log.ERROR, tag, android.util.Log.getStackTraceString(tr));
- }
-
- /**
- * What a Terrible Failure: Report a condition that should never happen.
- * The error will always be logged at level ASSERT with the call stack.
- * Depending on system configuration, a report may be added to the
- * {@link android.os.DropBoxManager} and/or the process may be terminated
- * immediately with an error dialog.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- */
- public static void wtf(final String tag, final String msg) {
- // Make sure this goes into our log buffer
- println(android.util.Log.ASSERT, tag, "wtf\n" + msg);
- android.util.Log.wtf(tag, msg, new Exception());
- }
-
- /**
- * What a Terrible Failure: Report a condition that should never happen.
- * The error will always be logged at level ASSERT with the call stack.
- * Depending on system configuration, a report may be added to the
- * {@link android.os.DropBoxManager} and/or the process may be terminated
- * immediately with an error dialog.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- * @param tr An exception to log
- */
- public static void wtf(final String tag, final String msg, final Throwable tr) {
- // Make sure this goes into our log buffer
- println(android.util.Log.ASSERT, tag, "wtf\n" + msg + '\n' +
- android.util.Log.getStackTraceString(tr));
- android.util.Log.wtf(tag, msg, tr);
- }
-
- /**
- * Low-level logging call.
- * @param level The priority/type of this log message
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- */
- private static void println(final int level, final String tag, final String msg) {
- android.util.Log.println(level, tag, msg);
-
- LogSaver serviceLog = sDebugLogSaver;
- if (serviceLog != null && level >= android.util.Log.DEBUG) {
- serviceLog.log(level, tag, msg);
- }
- }
-
- /**
- * Save logging into LogSaver only, for dumping to bug report
- *
- * @param level The priority/type of this log message
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param msg The message you would like logged.
- */
- public static void save(final int level, final String tag, final String msg) {
- LogSaver serviceLog = sDebugLogSaver;
- if (serviceLog != null) {
- serviceLog.log(level, tag, msg);
- }
- }
-
- /**
- * Checks to see whether or not a log for the specified tag is loggable at the specified level.
- * See {@link android.util.Log#isLoggable(String, int)} for more discussion.
- */
- public static boolean isLoggable(final String tag, final int level) {
- return android.util.Log.isLoggable(tag, level);
- }
-
- /**
- * Returns text as is if {@value #BUGLE_TAG}'s log level is set to DEBUG or VERBOSE;
- * returns "--" otherwise. Useful for log statements where we don't want to log
- * various strings (e.g., usernames) with default logging to avoid leaking PII in logcat.
- */
- public static String sanitizePII(final String text) {
- if (text == null) {
- return null;
- }
-
- if (android.util.Log.isLoggable(BUGLE_TAG, android.util.Log.DEBUG)) {
- return text;
- } else {
- return "Redacted-" + text.length();
- }
- }
-
- public static void dump(java.io.PrintWriter out) {
- final LogSaver logsaver = sDebugLogSaver;
- if (logsaver != null) {
- logsaver.dump(out);
- }
- }
-}
diff --git a/src/com/android/messaging/util/LoggingTimer.java b/src/com/android/messaging/util/LoggingTimer.java
deleted file mode 100644
index d0d41ac..0000000
--- a/src/com/android/messaging/util/LoggingTimer.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.os.SystemClock;
-
-/**
- * A utility timer that logs the execution time of operations
- */
-public class LoggingTimer {
- private static final int NO_WARN_LIMIT = -1;
-
- private final String mTag;
- private final String mName;
- private final long mWarnLimitMillis;
- private long mStartMillis;
-
- public LoggingTimer(final String tag, final String name) {
- this(tag, name, NO_WARN_LIMIT);
- }
-
- public LoggingTimer(final String tag, final String name, final long warnLimitMillis) {
- mTag = tag;
- mName = name;
- mWarnLimitMillis = warnLimitMillis;
- }
-
- /**
- * This method should be called at the start of the operation to be timed.
- */
- public void start() {
- mStartMillis = SystemClock.elapsedRealtime();
-
- if (LogUtil.isLoggable(mTag, LogUtil.VERBOSE)) {
- LogUtil.v(mTag, "Timer start for " + mName);
- }
- }
-
- /**
- * This method should be called at the end of the operation to be timed. It logs the time since
- * the last call to {@link #start}
- */
- public void stopAndLog() {
- final long elapsedMs = SystemClock.elapsedRealtime() - mStartMillis;
-
- final String logMessage = String.format("Used %dms for %s", elapsedMs, mName);
-
- LogUtil.save(LogUtil.DEBUG, mTag, logMessage);
-
- if (mWarnLimitMillis != NO_WARN_LIMIT && elapsedMs > mWarnLimitMillis) {
- LogUtil.w(mTag, logMessage);
- } else if (LogUtil.isLoggable(mTag, LogUtil.VERBOSE)) {
- LogUtil.v(mTag, logMessage);
- }
- }
-}
diff --git a/src/com/android/messaging/util/LongSparseSet.java b/src/com/android/messaging/util/LongSparseSet.java
deleted file mode 100644
index 8e2cfca..0000000
--- a/src/com/android/messaging/util/LongSparseSet.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.support.v4.util.LongSparseArray;
-
-/**
- * A space saving set for long values using v4 compat LongSparseArray
- */
-public class LongSparseSet {
- private static final Object THE_ONLY_VALID_VALUE = new Object();
- private final LongSparseArray<Object> mSet = new LongSparseArray<Object>();
-
- public LongSparseSet() {
- }
-
- /**
- * @param key The element to check
- * @return True if the element is in the set, false otherwise
- */
- public boolean contains(long key) {
- if (mSet.get(key, null/*default*/) == THE_ONLY_VALID_VALUE) {
- return true;
- }
- return false;
- }
-
- /**
- * Add an element to the set
- *
- * @param key The element to add
- */
- public void add(long key) {
- mSet.put(key, THE_ONLY_VALID_VALUE);
- }
-
- /**
- * Remove an element from the set
- *
- * @param key The element to remove
- */
- public void remove(long key) {
- mSet.delete(key);
- }
-}
diff --git a/src/com/android/messaging/util/MaterialPalette.java b/src/com/android/messaging/util/MaterialPalette.java
deleted file mode 100644
index 8dbacbf..0000000
--- a/src/com/android/messaging/util/MaterialPalette.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-
-public class MaterialPalette{
- public final int mPrimaryColor;
- public final int mSecondaryColor;
-
- public MaterialPalette(final int primaryColor, final int secondaryColor) {
- mPrimaryColor = primaryColor;
- mSecondaryColor = secondaryColor;
- }
-}
diff --git a/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java b/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java
deleted file mode 100644
index b1078d1..0000000
--- a/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.ContentResolver;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Bitmap;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-
-import java.io.IOException;
-
-/**
- * Convenience wrapper for {@link MediaMetadataRetriever} to help with its eccentric error handling.
- */
-public class MediaMetadataRetrieverWrapper {
- private final MediaMetadataRetriever mRetriever = new MediaMetadataRetriever();
-
- public MediaMetadataRetrieverWrapper() {
- }
-
- public void setDataSource(Uri uri) throws IOException {
- ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- AssetFileDescriptor fd = resolver.openAssetFileDescriptor(uri, "r");
- if (fd == null) {
- throw new IOException("openAssetFileDescriptor returned null for " + uri);
- }
- try {
- mRetriever.setDataSource(fd.getFileDescriptor());
- } catch (RuntimeException e) {
- release();
- throw new IOException(e);
- } finally {
- fd.close();
- }
- }
-
- public int extractInteger(final int key, final int defaultValue) {
- final String s = mRetriever.extractMetadata(key);
- if (TextUtils.isEmpty(s)) {
- return defaultValue;
- }
- return Integer.parseInt(s);
- }
-
- public String extractMetadata(final int key) {
- return mRetriever.extractMetadata(key);
- }
-
- public Bitmap getFrameAtTime() {
- return mRetriever.getFrameAtTime();
- }
-
- public Bitmap getFrameAtTime(final long timeUs) {
- return mRetriever.getFrameAtTime(timeUs);
- }
-
- public void release() {
- try {
- mRetriever.release();
- } catch (RuntimeException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "MediaMetadataRetriever.release failed", e);
- }
- }
-}
diff --git a/src/com/android/messaging/util/MediaUtil.java b/src/com/android/messaging/util/MediaUtil.java
deleted file mode 100644
index f25354c..0000000
--- a/src/com/android/messaging/util/MediaUtil.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.Context;
-
-import com.android.messaging.Factory;
-
-public abstract class MediaUtil {
- public static interface OnCompletionListener {
- public void onCompletion();
- }
-
- public static MediaUtil get() {
- return Factory.get().getMediaUtil();
- }
-
- /**
- * Play sound from local resources given a resource id.
- */
- public abstract void playSound(final Context context, final int resId,
- final OnCompletionListener completionListener);
-}
diff --git a/src/com/android/messaging/util/MediaUtilImpl.java b/src/com/android/messaging/util/MediaUtilImpl.java
deleted file mode 100644
index 272a057..0000000
--- a/src/com/android/messaging/util/MediaUtilImpl.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-
-/**
- * Default implementation of MediaUtil
- */
-public class MediaUtilImpl extends MediaUtil {
-
- @Override
- public void playSound(final Context context, final int resId,
- final OnCompletionListener completionListener) {
- // We want to play at the media volume and not the ringer volume, but we do want to
- // avoid playing sound when the ringer/notifications are silenced. This is used for
- // in app sounds that are not critical and should not impact running silent but also
- // shouldn't play at full ring volume if you want to hear your ringer but don't want
- // to be annoyed with in-app volume.
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- try {
- final MediaPlayer mediaPlayer = new MediaPlayer();
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
- final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
- mediaPlayer.setDataSource(
- afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- afd.close();
- mediaPlayer.prepare();
- mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(final MediaPlayer mp) {
- if (completionListener != null) {
- completionListener.onCompletion();
- }
- mp.stop();
- mp.release();
- }
- });
- mediaPlayer.seekTo(0);
- mediaPlayer.start();
- return;
- } catch (final Exception e) {
- LogUtil.w("MediaUtilImpl", "Error playing sound id: " + resId, e);
- }
- if (completionListener != null) {
- // Call the completion handler to not block functionality if audio play fails
- completionListener.onCompletion();
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/util/NotificationPlayer.java b/src/com/android/messaging/util/NotificationPlayer.java
deleted file mode 100644
index a4ed44e..0000000
--- a/src/com/android/messaging/util/NotificationPlayer.java
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.net.Uri;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemClock;
-
-import com.android.messaging.Factory;
-
-import java.util.LinkedList;
-
-/**
- * This class is provides the same interface and functionality as android.media.AsyncPlayer
- * with the following differences:
- * - whenever audio is played, audio focus is requested,
- * - whenever audio playback is stopped or the playback completed, audio focus is abandoned.
- *
- * This file has been copied from com.android.server.NotificationPlayer. The only modification is
- * the addition of a volume parameter. Hopefully the framework will adapt AsyncPlayer to support
- * all the functionality in this class, at which point this one can be deleted.
- */
-public class NotificationPlayer implements OnCompletionListener {
- private static final int PLAY = 1;
- private static final int STOP = 2;
- private static final boolean mDebug = false;
-
- private static final class Command {
- int code;
- Uri uri;
- boolean looping;
- int stream;
- float volume;
- long requestTime;
- boolean releaseFocus;
-
- @Override
- public String toString() {
- return "{ code=" + code + " looping=" + looping + " stream=" + stream
- + " uri=" + uri + " }";
- }
- }
-
- private final LinkedList<Command> mCmdQueue = new LinkedList<Command>();
-
- private Looper mLooper;
-
- /*
- * Besides the use of audio focus, the only implementation difference between AsyncPlayer and
- * NotificationPlayer resides in the creation of the MediaPlayer. For the completion callback,
- * OnCompletionListener, to be called at the end of the playback, the MediaPlayer needs to
- * be created with a looper running so its event handler is not null.
- */
- private final class CreationAndCompletionThread extends Thread {
- public Command mCmd;
- public CreationAndCompletionThread(final Command cmd) {
- super();
- mCmd = cmd;
- }
-
- @Override
- public void run() {
- Looper.prepare();
- mLooper = Looper.myLooper();
- synchronized (this) {
- final AudioManager audioManager =
- (AudioManager) Factory.get().getApplicationContext()
- .getSystemService(Context.AUDIO_SERVICE);
- try {
- final MediaPlayer player = new MediaPlayer();
- player.setAudioStreamType(mCmd.stream);
- player.setDataSource(Factory.get().getApplicationContext(), mCmd.uri);
- player.setLooping(mCmd.looping);
- player.setVolume(mCmd.volume, mCmd.volume);
- player.prepare();
- if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
- && (mCmd.uri.getEncodedPath().length() > 0)) {
- audioManager.requestAudioFocus(null, mCmd.stream,
- mCmd.looping ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
- : AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
- }
- player.setOnCompletionListener(NotificationPlayer.this);
- player.start();
- if (mPlayer != null) {
- mPlayer.release();
- }
- mPlayer = player;
- } catch (final Exception e) {
- LogUtil.w(mTag, "error loading sound for " + mCmd.uri, e);
- }
- mAudioManager = audioManager;
- this.notify();
- }
- Looper.loop();
- }
- }
-
- private void startSound(final Command cmd) {
- // Preparing can be slow, so if there is something else
- // is playing, let it continue until we're done, so there
- // is less of a glitch.
- try {
- if (mDebug) {
- LogUtil.d(mTag, "Starting playback");
- }
- //-----------------------------------
- // This is were we deviate from the AsyncPlayer implementation and create the
- // MediaPlayer in a new thread with which we're synchronized
- synchronized (mCompletionHandlingLock) {
- // if another sound was already playing, it doesn't matter we won't get notified
- // of the completion, since only the completion notification of the last sound
- // matters
- if ((mLooper != null)
- && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
- mLooper.quit();
- }
- mCompletionThread = new CreationAndCompletionThread(cmd);
- synchronized (mCompletionThread) {
- mCompletionThread.start();
- mCompletionThread.wait();
- }
- }
- //-----------------------------------
-
- final long delay = SystemClock.elapsedRealtime() - cmd.requestTime;
- if (delay > 1000) {
- LogUtil.w(mTag, "Notification sound delayed by " + delay + "msecs");
- }
- } catch (final Exception e) {
- LogUtil.w(mTag, "error loading sound for " + cmd.uri, e);
- }
- }
-
- private void stopSound(final Command cmd) {
- if (mPlayer == null) {
- return;
- }
- final long delay = SystemClock.elapsedRealtime() - cmd.requestTime;
- if (delay > 1000) {
- LogUtil.w(mTag, "Notification stop delayed by " + delay + "msecs");
- }
- mPlayer.stop();
- mPlayer.release();
- mPlayer = null;
- if (cmd.releaseFocus && mAudioManager != null) {
- mAudioManager.abandonAudioFocus(null);
- }
- mAudioManager = null;
- if ((mLooper != null) && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
- mLooper.quit();
- }
- }
-
- private final class CmdThread extends java.lang.Thread {
- CmdThread() {
- super("NotificationPlayer-" + mTag);
- }
-
- @Override
- public void run() {
- while (true) {
- Command cmd = null;
-
- synchronized (mCmdQueue) {
- if (mDebug) {
- LogUtil.d(mTag, "RemoveFirst");
- }
- cmd = mCmdQueue.removeFirst();
- }
-
- switch (cmd.code) {
- case PLAY:
- if (mDebug) {
- LogUtil.d(mTag, "PLAY");
- }
- startSound(cmd);
- break;
- case STOP:
- if (mDebug) {
- LogUtil.d(mTag, "STOP");
- }
- stopSound(cmd);
- break;
- }
-
- synchronized (mCmdQueue) {
- if (mCmdQueue.size() == 0) {
- // nothing left to do, quit
- // doing this check after we're done prevents the case where they
- // added it during the operation from spawning two threads and
- // trying to do them in parallel.
- mThread = null;
- releaseWakeLock();
- return;
- }
- }
- }
- }
- }
-
- @Override
- public void onCompletion(final MediaPlayer mp) {
- if (mAudioManager != null) {
- mAudioManager.abandonAudioFocus(null);
- }
- // if there are no more sounds to play, end the Looper to listen for media completion
- synchronized (mCmdQueue) {
- if (mCmdQueue.size() == 0) {
- synchronized (mCompletionHandlingLock) {
- if (mLooper != null) {
- mLooper.quit();
- }
- mCompletionThread = null;
- }
- }
- }
- }
-
- private String mTag;
- private CmdThread mThread;
- private CreationAndCompletionThread mCompletionThread;
- private final Object mCompletionHandlingLock = new Object();
- private MediaPlayer mPlayer;
- private PowerManager.WakeLock mWakeLock;
- private AudioManager mAudioManager;
-
- // The current state according to the caller. Reality lags behind
- // because of the asynchronous nature of this class.
- private int mState = STOP;
-
- /**
- * Construct a NotificationPlayer object.
- *
- * @param tag a string to use for debugging
- */
- public NotificationPlayer(final String tag) {
- if (tag != null) {
- mTag = tag;
- } else {
- mTag = "NotificationPlayer";
- }
- }
-
- /**
- * Start playing the sound. It will actually start playing at some
- * point in the future. There are no guarantees about latency here.
- * Calling this before another audio file is done playing will stop
- * that one and start the new one.
- *
- * @param uri The URI to play. (see {@link MediaPlayer#setDataSource(Context, Uri)})
- * @param looping Whether the audio should loop forever.
- * (see {@link MediaPlayer#setLooping(boolean)})
- * @param stream the AudioStream to use.
- * (see {@link MediaPlayer#setAudioStreamType(int)})
- * @param volume The volume at which to play this sound, as a fraction of the system volume for
- * the relevant stream type. A value of 1 is the maximum and means play at the system
- * volume with no attenuation.
- */
- public void play(final Uri uri, final boolean looping, final int stream, final float volume) {
- final Command cmd = new Command();
- cmd.requestTime = SystemClock.elapsedRealtime();
- cmd.code = PLAY;
- cmd.uri = uri;
- cmd.looping = looping;
- cmd.stream = stream;
- cmd.volume = volume;
- synchronized (mCmdQueue) {
- enqueueLocked(cmd);
- mState = PLAY;
- }
- }
-
- /** Same as calling stop(true) */
- public void stop() {
- stop(true);
- }
-
- /**
- * Stop a previously played sound. It can't be played again or unpaused
- * at this point. Calling this multiple times has no ill effects.
- * @param releaseAudioFocus whether to release audio focus
- */
- public void stop(final boolean releaseAudioFocus) {
- synchronized (mCmdQueue) {
- // This check allows stop to be called multiple times without starting
- // a thread that ends up doing nothing.
- if (mState != STOP) {
- final Command cmd = new Command();
- cmd.requestTime = SystemClock.elapsedRealtime();
- cmd.code = STOP;
- cmd.releaseFocus = releaseAudioFocus;
- enqueueLocked(cmd);
- mState = STOP;
- }
- }
- }
-
- private void enqueueLocked(final Command cmd) {
- mCmdQueue.add(cmd);
- if (mThread == null) {
- acquireWakeLock();
- mThread = new CmdThread();
- mThread.start();
- }
- }
-
- /**
- * We want to hold a wake lock while we do the prepare and play. The stop probably is
- * optional, but it won't hurt to have it too. The problem is that if you start a sound
- * while you're holding a wake lock (e.g. an alarm starting a notification), you want the
- * sound to play, but if the CPU turns off before mThread gets to work, it won't. The
- * simplest way to deal with this is to make it so there is a wake lock held while the
- * thread is starting or running. You're going to need the WAKE_LOCK permission if you're
- * going to call this.
- *
- * This must be called before the first time play is called.
- *
- * @hide
- */
- public void setUsesWakeLock() {
- if (mWakeLock != null || mThread != null) {
- // if either of these has happened, we've already played something.
- // and our releases will be out of sync.
- throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
- + " mThread=" + mThread);
- }
- final PowerManager pm = (PowerManager) Factory.get().getApplicationContext()
- .getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
- }
-
- private void acquireWakeLock() {
- if (mWakeLock != null) {
- mWakeLock.acquire();
- }
- }
-
- private void releaseWakeLock() {
- if (mWakeLock != null) {
- mWakeLock.release();
- }
- }
-}
-
diff --git a/src/com/android/messaging/util/OsUtil.java b/src/com/android/messaging/util/OsUtil.java
deleted file mode 100644
index e45a63c..0000000
--- a/src/com/android/messaging/util/OsUtil.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import com.android.messaging.Factory;
-
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.Set;
-
-/**
- * Android OS version utilities
- */
-public class OsUtil {
- private static boolean sIsAtLeastICS_MR1;
- private static boolean sIsAtLeastJB;
- private static boolean sIsAtLeastJB_MR1;
- private static boolean sIsAtLeastJB_MR2;
- private static boolean sIsAtLeastKLP;
- private static boolean sIsAtLeastL;
- private static boolean sIsAtLeastL_MR1;
- private static boolean sIsAtLeastM;
-
- private static Boolean sIsSecondaryUser = null;
-
- static {
- final int v = getApiVersion();
- sIsAtLeastICS_MR1 = v >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
- sIsAtLeastJB = v >= android.os.Build.VERSION_CODES.JELLY_BEAN;
- sIsAtLeastJB_MR1 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
- sIsAtLeastJB_MR2 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
- sIsAtLeastKLP = v >= android.os.Build.VERSION_CODES.KITKAT;
- sIsAtLeastL = v >= android.os.Build.VERSION_CODES.LOLLIPOP;
- sIsAtLeastL_MR1 = v >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
- sIsAtLeastM = v >= android.os.Build.VERSION_CODES.M;
- }
-
- /**
- * @return True if the version of Android that we're running on is at least Ice Cream Sandwich
- * MR1 (API level 15).
- */
- public static boolean isAtLeastICS_MR1() {
- return sIsAtLeastICS_MR1;
- }
-
- /**
- * @return True if the version of Android that we're running on is at least Jelly Bean
- * (API level 16).
- */
- public static boolean isAtLeastJB() {
- return sIsAtLeastJB;
- }
-
- /**
- * @return True if the version of Android that we're running on is at least Jelly Bean MR1
- * (API level 17).
- */
- public static boolean isAtLeastJB_MR1() {
- return sIsAtLeastJB_MR1;
- }
-
- /**
- * @return True if the version of Android that we're running on is at least Jelly Bean MR2
- * (API level 18).
- */
- public static boolean isAtLeastJB_MR2() {
- return sIsAtLeastJB_MR2;
- }
-
- /**
- * @return True if the version of Android that we're running on is at least KLP
- * (API level 19).
- */
- public static boolean isAtLeastKLP() {
- return sIsAtLeastKLP;
- }
-
- /**
- * @return True if the version of Android that we're running on is at least L
- * (API level 21).
- */
- public static boolean isAtLeastL() {
- return sIsAtLeastL;
- }
-
- /**
- * @return True if the version of Android that we're running on is at least L MR1
- * (API level 22).
- */
- public static boolean isAtLeastL_MR1() {
- return sIsAtLeastL_MR1;
- }
-
- /**
- * @return True if the version of Android that we're running on is at least M
- * (API level 23).
- */
- public static boolean isAtLeastM() {
- return sIsAtLeastM;
- }
-
- /**
- * @return The Android API version of the OS that we're currently running on.
- */
- public static int getApiVersion() {
- return android.os.Build.VERSION.SDK_INT;
- }
-
- public static boolean isSecondaryUser() {
- if (sIsSecondaryUser == null) {
- final Context context = Factory.get().getApplicationContext();
- boolean isSecondaryUser = false;
-
- // Only check for newer devices (but not the nexus 10)
- if (OsUtil.sIsAtLeastJB_MR1 && !"Nexus 10".equals(Build.MODEL)) {
- final UserHandle uh = android.os.Process.myUserHandle();
- final UserManager userManager =
- (UserManager) context.getSystemService(Context.USER_SERVICE);
- if (userManager != null) {
- final long userSerialNumber = userManager.getSerialNumberForUser(uh);
- isSecondaryUser = (0 != userSerialNumber);
- }
- }
- sIsSecondaryUser = isSecondaryUser;
- }
- return sIsSecondaryUser;
- }
-
- /**
- * Creates a joined string from a Set<String> using the given delimiter.
- * @param values
- * @param delimiter
- * @return
- */
- public static String joinFromSetWithDelimiter(
- final Set<String> values, final String delimiter) {
- if (values != null) {
- final StringBuilder joinedStringBuilder = new StringBuilder();
- boolean firstValue = true;
- for (final String value : values) {
- if (firstValue) {
- firstValue = false;
- } else {
- joinedStringBuilder.append(delimiter);
- }
- joinedStringBuilder.append(value);
- }
- return joinedStringBuilder.toString();
- }
- return null;
- }
-
- private static Hashtable<String, Integer> sPermissions = new Hashtable<String, Integer>();
-
- /**
- * Check if the app has the specified permission. If it does not, the app needs to use
- * {@link android.app.Activity#requestPermission}. Note that if it
- * returns true, it cannot return false in the same process as the OS kills the process when
- * any permission is revoked.
- * @param permission A permission from {@link android.Manifest.permission}
- */
- public static boolean hasPermission(final String permission) {
- if (OsUtil.isAtLeastM()) {
- // It is safe to cache the PERMISSION_GRANTED result as the process gets killed if the
- // user revokes the permission setting. However, PERMISSION_DENIED should not be
- // cached as the process does not get killed if the user enables the permission setting.
- if (!sPermissions.containsKey(permission)
- || sPermissions.get(permission) == PackageManager.PERMISSION_DENIED) {
- final Context context = Factory.get().getApplicationContext();
- final int permissionState = context.checkSelfPermission(permission);
- sPermissions.put(permission, permissionState);
- }
- return sPermissions.get(permission) == PackageManager.PERMISSION_GRANTED;
- } else {
- return true;
- }
- }
-
- /** Does the app have all the specified permissions */
- public static boolean hasPermissions(final String[] permissions) {
- for (final String permission : permissions) {
- if (!hasPermission(permission)) {
- return false;
- }
- }
- return true;
- }
-
- public static boolean hasPhonePermission() {
- return hasPermission(Manifest.permission.READ_PHONE_STATE);
- }
-
- public static boolean hasSmsPermission() {
- return hasPermission(Manifest.permission.READ_SMS);
- }
-
- public static boolean hasLocationPermission() {
- return OsUtil.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION);
- }
-
-
- public static boolean hasStoragePermission() {
- // Note that READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are granted or denied
- // together.
- return OsUtil.hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
- }
-
- public static boolean hasRecordAudioPermission() {
- return OsUtil.hasPermission(Manifest.permission.RECORD_AUDIO);
- }
-
- /**
- * Returns array with the set of permissions that have not been granted from the given set.
- * The array will be empty if the app has all of the specified permissions. Note that calling
- * {@link Activity#requestPermissions} for an already granted permission can prompt the user
- * again, and its up to the app to only request permissions that are missing.
- */
- public static String[] getMissingPermissions(final String[] permissions) {
- final ArrayList<String> missingList = new ArrayList<String>();
- for (final String permission : permissions) {
- if (!hasPermission(permission)) {
- missingList.add(permission);
- }
- }
-
- final String[] missingArray = new String[missingList.size()];
- missingList.toArray(missingArray);
- return missingArray;
- }
-
- private static String[] sRequiredPermissions = new String[] {
- // Required to read existing SMS threads
- Manifest.permission.READ_SMS,
- // Required for knowing the phone number, number of SIMs, etc.
- Manifest.permission.READ_PHONE_STATE,
- // This is not strictly required, but simplifies the contact picker scenarios
- Manifest.permission.READ_CONTACTS,
- };
-
- /** Does the app have the minimum set of permissions required to operate. */
- public static boolean hasRequiredPermissions() {
- return hasPermissions(sRequiredPermissions);
- }
-
- public static String[] getMissingRequiredPermissions() {
- return getMissingPermissions(sRequiredPermissions);
- }
-}
diff --git a/src/com/android/messaging/util/PendingIntentConstants.java b/src/com/android/messaging/util/PendingIntentConstants.java
deleted file mode 100644
index 1a594c5..0000000
--- a/src/com/android/messaging/util/PendingIntentConstants.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-
-public class PendingIntentConstants {
- // Notifications
- public static final int SMS_NOTIFICATION_ID = 0;
- public static final int SMS_SECONDARY_USER_NOTIFICATION_ID = 1;
- public static final int MSG_SEND_ERROR = 2;
- public static final int SMS_STORAGE_LOW_NOTIFICATION_ID = 3;
-
- // Request codes
- public static final int UPDATE_NOTIFICATIONS_ALARM_ACTION_ID = 100;
-
- public static final int MIN_ASSIGNED_REQUEST_CODE = 1001;
-
- // Logging
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final boolean VERBOSE = false;
-
- // Internal Constants
- private static final String NOTIFICATION_REQUEST_CODE_PREFS = "notificationRequestCodes.v1";
- private static final String REQUEST_CODE_DELIMITER = "|";
- private static final String MAX_REQUEST_CODE_KEY = "maxRequestCode";
-}
diff --git a/src/com/android/messaging/util/PhoneUtils.java b/src/com/android/messaging/util/PhoneUtils.java
deleted file mode 100644
index 726f083..0000000
--- a/src/com/android/messaging/util/PhoneUtils.java
+++ /dev/null
@@ -1,1011 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.Cursor;
-import android.net.ConnectivityManager;
-import android.provider.Settings;
-import android.provider.Telephony;
-import android.support.v4.util.ArrayMap;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SmsManager;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.google.i18n.phonenumbers.NumberParseException;
-import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
-import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * This class abstracts away platform dependency of calling telephony related
- * platform APIs, mostly involving TelephonyManager, SubscriptionManager and
- * a bit of SmsManager.
- *
- * The class instance can only be obtained via the get(int subId) method parameterized
- * by a SIM subscription ID. On pre-L_MR1, the subId is not used and it has to be
- * the default subId (-1).
- *
- * A convenient getDefault() method is provided for default subId (-1) on any platform
- */
-public abstract class PhoneUtils {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final int MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT = 6;
-
- private static final List<SubscriptionInfo> EMPTY_SUBSCRIPTION_LIST = new ArrayList<>();
-
- // The canonical phone number cache
- // Each country gets its own cache. The following maps from ISO country code to
- // the country's cache. Each cache maps from original phone number to canonicalized phone
- private static final ArrayMap<String, ArrayMap<String, String>> sCanonicalPhoneNumberCache =
- new ArrayMap<>();
-
- protected final Context mContext;
- protected final TelephonyManager mTelephonyManager;
- protected final int mSubId;
-
- public PhoneUtils(int subId) {
- mSubId = subId;
- mContext = Factory.get().getApplicationContext();
- mTelephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- }
-
- /**
- * Get the SIM's country code
- *
- * @return the country code on the SIM
- */
- public abstract String getSimCountry();
-
- /**
- * Get number of SIM slots
- *
- * @return the SIM slot count
- */
- public abstract int getSimSlotCount();
-
- /**
- * Get SIM's carrier name
- *
- * @return the carrier name of the SIM
- */
- public abstract String getCarrierName();
-
- /**
- * Check if there is SIM inserted on the device
- *
- * @return true if there is SIM inserted, false otherwise
- */
- public abstract boolean hasSim();
-
- /**
- * Check if the SIM is roaming
- *
- * @return true if the SIM is in romaing state, false otherwise
- */
- public abstract boolean isRoaming();
-
- /**
- * Get the MCC and MNC in integer of the SIM's provider
- *
- * @return an array of two ints, [0] is the MCC code and [1] is the MNC code
- */
- public abstract int[] getMccMnc();
-
- /**
- * Get the mcc/mnc string
- *
- * @return the text of mccmnc string
- */
- public abstract String getSimOperatorNumeric();
-
- /**
- * Get the SIM's self raw number, i.e. not canonicalized
- *
- * @param allowOverride Whether to use the app's setting to override the self number
- * @return the original self number
- * @throws IllegalStateException if no active subscription on L-MR1+
- */
- public abstract String getSelfRawNumber(final boolean allowOverride);
-
- /**
- * Returns the "effective" subId, or the subId used in the context of actual messages,
- * conversations and subscription-specific settings, for the given "nominal" sub id.
- *
- * For pre-L-MR1 platform, this should always be
- * {@value com.android.messaging.datamodel.data.ParticipantData#DEFAULT_SELF_SUB_ID};
- *
- * On the other hand, for L-MR1 and above, DEFAULT_SELF_SUB_ID will be mapped to the system
- * default subscription id for SMS.
- *
- * @param subId The input subId
- * @return the real subId if we can convert
- */
- public abstract int getEffectiveSubId(int subId);
-
- /**
- * Returns the number of active subscriptions in the device.
- */
- public abstract int getActiveSubscriptionCount();
-
- /**
- * Get {@link SmsManager} instance
- *
- * @return the relevant SmsManager instance based on OS version and subId
- */
- public abstract SmsManager getSmsManager();
-
- /**
- * Get the default SMS subscription id
- *
- * @return the default sub ID
- */
- public abstract int getDefaultSmsSubscriptionId();
-
- /**
- * Returns if there's currently a system default SIM selected for sending SMS.
- */
- public abstract boolean getHasPreferredSmsSim();
-
- /**
- * For L_MR1, system may return a negative subId. Convert this into our own
- * subId, so that we consistently use -1 for invalid or default.
- *
- * see b/18629526 and b/18670346
- *
- * @param intent The push intent from system
- * @param extraName The name of the sub id extra
- * @return the subId that is valid and meaningful for the app
- */
- public abstract int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName);
-
- /**
- * Get the subscription_id column value from a telephony provider cursor
- *
- * @param cursor The database query cursor
- * @param subIdIndex The index of the subId column in the cursor
- * @return the subscription_id column value from the cursor
- */
- public abstract int getSubIdFromTelephony(Cursor cursor, int subIdIndex);
-
- /**
- * Check if data roaming is enabled
- *
- * @return true if data roaming is enabled, false otherwise
- */
- public abstract boolean isDataRoamingEnabled();
-
- /**
- * Check if mobile data is enabled
- *
- * @return true if mobile data is enabled, false otherwise
- */
- public abstract boolean isMobileDataEnabled();
-
- /**
- * Get the set of self phone numbers, all normalized
- *
- * @return the set of normalized self phone numbers
- */
- public abstract HashSet<String> getNormalizedSelfNumbers();
-
- /**
- * This interface packages methods should only compile on L_MR1.
- * This is needed to make unit tests happy when mockito tries to
- * mock these methods. Calling on these methods on L_MR1 requires
- * an extra invocation of toMr1().
- */
- public interface LMr1 {
- /**
- * Get this SIM's information. Only applies to L_MR1 above
- *
- * @return the subscription info of the SIM
- */
- public abstract SubscriptionInfo getActiveSubscriptionInfo();
-
- /**
- * Get the list of active SIMs in system. Only applies to L_MR1 above
- *
- * @return the list of subscription info for all inserted SIMs
- */
- public abstract List<SubscriptionInfo> getActiveSubscriptionInfoList();
-
- /**
- * Register subscription change listener. Only applies to L_MR1 above
- *
- * @param listener The listener to register
- */
- public abstract void registerOnSubscriptionsChangedListener(
- SubscriptionManager.OnSubscriptionsChangedListener listener);
- }
-
- /**
- * The PhoneUtils class for pre L_MR1
- */
- public static class PhoneUtilsPreLMR1 extends PhoneUtils {
- private final ConnectivityManager mConnectivityManager;
-
- public PhoneUtilsPreLMR1() {
- super(ParticipantData.DEFAULT_SELF_SUB_ID);
- mConnectivityManager =
- (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- }
-
- @Override
- public String getSimCountry() {
- final String country = mTelephonyManager.getSimCountryIso();
- if (TextUtils.isEmpty(country)) {
- return null;
- }
- return country.toUpperCase();
- }
-
- @Override
- public int getSimSlotCount() {
- // Don't support MSIM pre-L_MR1
- return 1;
- }
-
- @Override
- public String getCarrierName() {
- return mTelephonyManager.getNetworkOperatorName();
- }
-
- @Override
- public boolean hasSim() {
- return mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT;
- }
-
- @Override
- public boolean isRoaming() {
- return mTelephonyManager.isNetworkRoaming();
- }
-
- @Override
- public int[] getMccMnc() {
- final String mccmnc = mTelephonyManager.getSimOperator();
- int mcc = 0;
- int mnc = 0;
- try {
- mcc = Integer.parseInt(mccmnc.substring(0, 3));
- mnc = Integer.parseInt(mccmnc.substring(3));
- } catch (Exception e) {
- LogUtil.w(TAG, "PhoneUtils.getMccMnc: invalid string " + mccmnc, e);
- }
- return new int[]{mcc, mnc};
- }
-
- @Override
- public String getSimOperatorNumeric() {
- return mTelephonyManager.getSimOperator();
- }
-
- @Override
- public String getSelfRawNumber(final boolean allowOverride) {
- if (allowOverride) {
- final String userDefinedNumber = getNumberFromPrefs(mContext,
- ParticipantData.DEFAULT_SELF_SUB_ID);
- if (!TextUtils.isEmpty(userDefinedNumber)) {
- return userDefinedNumber;
- }
- }
- return mTelephonyManager.getLine1Number();
- }
-
- @Override
- public int getEffectiveSubId(int subId) {
- Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId);
- return ParticipantData.DEFAULT_SELF_SUB_ID;
- }
-
- @Override
- public SmsManager getSmsManager() {
- return SmsManager.getDefault();
- }
-
- @Override
- public int getDefaultSmsSubscriptionId() {
- Assert.fail("PhoneUtils.getDefaultSmsSubscriptionId(): not supported before L MR1");
- return ParticipantData.DEFAULT_SELF_SUB_ID;
- }
-
- @Override
- public boolean getHasPreferredSmsSim() {
- // SIM selection is not supported pre-L_MR1.
- return true;
- }
-
- @Override
- public int getActiveSubscriptionCount() {
- return hasSim() ? 1 : 0;
- }
-
- @Override
- public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) {
- // Pre-L_MR1 always returns the default id
- return ParticipantData.DEFAULT_SELF_SUB_ID;
- }
-
- @Override
- public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) {
- // No subscription_id column before L_MR1
- return ParticipantData.DEFAULT_SELF_SUB_ID;
- }
-
- @Override
- @SuppressWarnings("deprecation")
- public boolean isDataRoamingEnabled() {
- boolean dataRoamingEnabled = false;
- final ContentResolver cr = mContext.getContentResolver();
- if (OsUtil.isAtLeastJB_MR1()) {
- dataRoamingEnabled =
- (Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0);
- } else {
- dataRoamingEnabled =
- (Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0);
- }
- return dataRoamingEnabled;
- }
-
- @Override
- public boolean isMobileDataEnabled() {
- boolean mobileDataEnabled = false;
- try {
- final Class cmClass = mConnectivityManager.getClass();
- final Method method = cmClass.getDeclaredMethod("getMobileDataEnabled");
- method.setAccessible(true); // Make the method callable
- // get the setting for "mobile data"
- mobileDataEnabled = (Boolean) method.invoke(mConnectivityManager);
- } catch (final Exception e) {
- LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e);
- }
- return mobileDataEnabled;
- }
-
- @Override
- public HashSet<String> getNormalizedSelfNumbers() {
- final HashSet<String> numbers = new HashSet<>();
- numbers.add(getCanonicalForSelf(true/*allowOverride*/));
- return numbers;
- }
- }
-
- /**
- * The PhoneUtils class for L_MR1
- */
- public static class PhoneUtilsLMR1 extends PhoneUtils implements LMr1 {
- private final SubscriptionManager mSubscriptionManager;
-
- public PhoneUtilsLMR1(final int subId) {
- super(subId);
- mSubscriptionManager = SubscriptionManager.from(Factory.get().getApplicationContext());
- }
-
- @Override
- public String getSimCountry() {
- final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
- if (subInfo != null) {
- final String country = subInfo.getCountryIso();
- if (TextUtils.isEmpty(country)) {
- return null;
- }
- return country.toUpperCase();
- }
- return null;
- }
-
- @Override
- public int getSimSlotCount() {
- return mSubscriptionManager.getActiveSubscriptionInfoCountMax();
- }
-
- @Override
- public String getCarrierName() {
- final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
- if (subInfo != null) {
- final CharSequence displayName = subInfo.getDisplayName();
- if (!TextUtils.isEmpty(displayName)) {
- return displayName.toString();
- }
- final CharSequence carrierName = subInfo.getCarrierName();
- if (carrierName != null) {
- return carrierName.toString();
- }
- }
- return null;
- }
-
- @Override
- public boolean hasSim() {
- return mSubscriptionManager.getActiveSubscriptionInfoCount() > 0;
- }
-
- @Override
- public boolean isRoaming() {
- return mSubscriptionManager.isNetworkRoaming(mSubId);
- }
-
- @Override
- public int[] getMccMnc() {
- int mcc = 0;
- int mnc = 0;
- final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
- if (subInfo != null) {
- mcc = subInfo.getMcc();
- mnc = subInfo.getMnc();
- }
- return new int[]{mcc, mnc};
- }
-
- @Override
- public String getSimOperatorNumeric() {
- // For L_MR1 we return the canonicalized (xxxxxx) string
- return getMccMncString(getMccMnc());
- }
-
- @Override
- public String getSelfRawNumber(final boolean allowOverride) {
- if (allowOverride) {
- final String userDefinedNumber = getNumberFromPrefs(mContext, mSubId);
- if (!TextUtils.isEmpty(userDefinedNumber)) {
- return userDefinedNumber;
- }
- }
-
- final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
- if (subInfo != null) {
- String phoneNumber = subInfo.getNumber();
- if (TextUtils.isEmpty(phoneNumber) && LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SubscriptionInfo phone number for self is empty!");
- }
- return phoneNumber;
- }
- LogUtil.w(TAG, "PhoneUtils.getSelfRawNumber: subInfo is null for " + mSubId);
- throw new IllegalStateException("No active subscription");
- }
-
- @Override
- public SubscriptionInfo getActiveSubscriptionInfo() {
- try {
- final SubscriptionInfo subInfo =
- mSubscriptionManager.getActiveSubscriptionInfo(mSubId);
- if (subInfo == null) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- // This is possible if the sub id is no longer available.
- LogUtil.d(TAG, "PhoneUtils.getActiveSubscriptionInfo(): empty sub info for "
- + mSubId);
- }
- }
- return subInfo;
- } catch (Exception e) {
- LogUtil.e(TAG, "PhoneUtils.getActiveSubscriptionInfo: system exception for "
- + mSubId, e);
- }
- return null;
- }
-
- @Override
- public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
- final List<SubscriptionInfo> subscriptionInfos =
- mSubscriptionManager.getActiveSubscriptionInfoList();
- if (subscriptionInfos != null) {
- return subscriptionInfos;
- }
- return EMPTY_SUBSCRIPTION_LIST;
- }
-
- @Override
- public int getEffectiveSubId(int subId) {
- if (subId == ParticipantData.DEFAULT_SELF_SUB_ID) {
- return getDefaultSmsSubscriptionId();
- }
- return subId;
- }
-
- @Override
- public void registerOnSubscriptionsChangedListener(
- SubscriptionManager.OnSubscriptionsChangedListener listener) {
- mSubscriptionManager.addOnSubscriptionsChangedListener(listener);
- }
-
- @Override
- public SmsManager getSmsManager() {
- return SmsManager.getSmsManagerForSubscriptionId(mSubId);
- }
-
- @Override
- public int getDefaultSmsSubscriptionId() {
- final int systemDefaultSubId = SmsManager.getDefaultSmsSubscriptionId();
- if (systemDefaultSubId < 0) {
- // Always use -1 for any negative subId from system
- return ParticipantData.DEFAULT_SELF_SUB_ID;
- }
- return systemDefaultSubId;
- }
-
- @Override
- public boolean getHasPreferredSmsSim() {
- return getDefaultSmsSubscriptionId() != ParticipantData.DEFAULT_SELF_SUB_ID;
- }
-
- @Override
- public int getActiveSubscriptionCount() {
- return mSubscriptionManager.getActiveSubscriptionInfoCount();
- }
-
- @Override
- public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) {
- return getEffectiveIncomingSubIdFromSystem(intent.getIntExtra(extraName,
- ParticipantData.DEFAULT_SELF_SUB_ID));
- }
-
- private int getEffectiveIncomingSubIdFromSystem(int subId) {
- if (subId < 0) {
- if (mSubscriptionManager.getActiveSubscriptionInfoCount() > 1) {
- // For multi-SIM device, we can not decide which SIM to use if system
- // does not know either. So just make it the invalid sub id.
- return ParticipantData.DEFAULT_SELF_SUB_ID;
- }
- // For single-SIM device, it must come from the only SIM we have
- return getDefaultSmsSubscriptionId();
- }
- return subId;
- }
-
- @Override
- public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) {
- return getEffectiveIncomingSubIdFromSystem(cursor.getInt(subIdIndex));
- }
-
- @Override
- public boolean isDataRoamingEnabled() {
- final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
- if (subInfo == null) {
- // There is nothing we can do if system give us empty sub info
- LogUtil.e(TAG, "PhoneUtils.isDataRoamingEnabled: system return empty sub info for "
- + mSubId);
- return false;
- }
- return subInfo.getDataRoaming() != SubscriptionManager.DATA_ROAMING_DISABLE;
- }
-
- @Override
- public boolean isMobileDataEnabled() {
- boolean mobileDataEnabled = false;
- try {
- final Class cmClass = mTelephonyManager.getClass();
- final Method method = cmClass.getDeclaredMethod("getDataEnabled", Integer.TYPE);
- method.setAccessible(true); // Make the method callable
- // get the setting for "mobile data"
- mobileDataEnabled = (Boolean) method.invoke(
- mTelephonyManager, Integer.valueOf(mSubId));
- } catch (final Exception e) {
- LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e);
- }
- return mobileDataEnabled;
-
- }
-
- @Override
- public HashSet<String> getNormalizedSelfNumbers() {
- final HashSet<String> numbers = new HashSet<>();
- for (SubscriptionInfo info : getActiveSubscriptionInfoList()) {
- numbers.add(PhoneUtils.get(info.getSubscriptionId()).getCanonicalForSelf(
- true/*allowOverride*/));
- }
- return numbers;
- }
- }
-
- /**
- * A convenient get() method that uses the default SIM. Use this when SIM is
- * not relevant, e.g. isDefaultSmsApp
- *
- * @return an instance of PhoneUtils for default SIM
- */
- public static PhoneUtils getDefault() {
- return Factory.get().getPhoneUtils(ParticipantData.DEFAULT_SELF_SUB_ID);
- }
-
- /**
- * Get an instance of PhoneUtils associated with a specific SIM, which is also platform
- * specific.
- *
- * @param subId The SIM's subscription ID
- * @return the instance
- */
- public static PhoneUtils get(int subId) {
- return Factory.get().getPhoneUtils(subId);
- }
-
- public LMr1 toLMr1() {
- if (OsUtil.isAtLeastL_MR1()) {
- return (LMr1) this;
- } else {
- Assert.fail("PhoneUtils.toLMr1(): invalid OS version");
- return null;
- }
- }
-
- /**
- * Check if this device supports SMS
- *
- * @return true if SMS is supported, false otherwise
- */
- public boolean isSmsCapable() {
- return mTelephonyManager.isSmsCapable();
- }
-
- /**
- * Check if this device supports voice calling
- *
- * @return true if voice calling is supported, false otherwise
- */
- public boolean isVoiceCapable() {
- return mTelephonyManager.isVoiceCapable();
- }
-
- /**
- * Get the ISO country code from system locale setting
- *
- * @return the ISO country code from system locale
- */
- private static String getLocaleCountry() {
- final String country = Locale.getDefault().getCountry();
- if (TextUtils.isEmpty(country)) {
- return null;
- }
- return country.toUpperCase();
- }
-
- /**
- * Get ISO country code from the SIM, if not available, fall back to locale
- *
- * @return SIM or locale ISO country code
- */
- public String getSimOrDefaultLocaleCountry() {
- String country = getSimCountry();
- if (country == null) {
- country = getLocaleCountry();
- }
- return country;
- }
-
- // Get or set the cache of canonicalized phone numbers for a specific country
- private static ArrayMap<String, String> getOrAddCountryMapInCacheLocked(String country) {
- if (country == null) {
- country = "";
- }
- ArrayMap<String, String> countryMap = sCanonicalPhoneNumberCache.get(country);
- if (countryMap == null) {
- countryMap = new ArrayMap<>();
- sCanonicalPhoneNumberCache.put(country, countryMap);
- }
- return countryMap;
- }
-
- // Get canonicalized phone number from cache
- private static String getCanonicalFromCache(final String phoneText, String country) {
- synchronized (sCanonicalPhoneNumberCache) {
- final ArrayMap<String, String> countryMap = getOrAddCountryMapInCacheLocked(country);
- return countryMap.get(phoneText);
- }
- }
-
- // Put canonicalized phone number into cache
- private static void putCanonicalToCache(final String phoneText, String country,
- final String canonical) {
- synchronized (sCanonicalPhoneNumberCache) {
- final ArrayMap<String, String> countryMap = getOrAddCountryMapInCacheLocked(country);
- countryMap.put(phoneText, canonical);
- }
- }
-
- /**
- * Utility method to parse user input number into standard E164 number.
- *
- * @param phoneText Phone number text as input by user.
- * @param country ISO country code based on which to parse the number.
- * @return E164 phone number. Returns null in case parsing failed.
- */
- private static String getValidE164Number(final String phoneText, final String country) {
- final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
- try {
- final PhoneNumber phoneNumber = phoneNumberUtil.parse(phoneText, country);
- if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) {
- return phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164);
- }
- } catch (final NumberParseException e) {
- LogUtil.e(TAG, "PhoneUtils.getValidE164Number(): Not able to parse phone number "
- + LogUtil.sanitizePII(phoneText) + " for country " + country);
- }
- return null;
- }
-
- /**
- * Canonicalize phone number using system locale country
- *
- * @param phoneText The phone number to canonicalize
- * @return the canonicalized number
- */
- public String getCanonicalBySystemLocale(final String phoneText) {
- return getCanonicalByCountry(phoneText, getLocaleCountry());
- }
-
- /**
- * Canonicalize phone number using SIM's country, may fall back to system locale country
- * if SIM country can not be obtained
- *
- * @param phoneText The phone number to canonicalize
- * @return the canonicalized number
- */
- public String getCanonicalBySimLocale(final String phoneText) {
- return getCanonicalByCountry(phoneText, getSimOrDefaultLocaleCountry());
- }
-
- /**
- * Canonicalize phone number using a country code.
- * This uses an internal cache per country to speed up.
- *
- * @param phoneText The phone number to canonicalize
- * @param country The ISO country code to use
- * @return the canonicalized number, or the original number if can't be parsed
- */
- private String getCanonicalByCountry(final String phoneText, final String country) {
- Assert.notNull(phoneText);
-
- String canonicalNumber = getCanonicalFromCache(phoneText, country);
- if (canonicalNumber != null) {
- return canonicalNumber;
- }
- canonicalNumber = getValidE164Number(phoneText, country);
- if (canonicalNumber == null) {
- // If we can't normalize this number, we just use the display string number.
- // This is possible for short codes and other non-localizable numbers.
- canonicalNumber = phoneText;
- }
- putCanonicalToCache(phoneText, country, canonicalNumber);
- return canonicalNumber;
- }
-
- /**
- * Canonicalize the self (per SIM) phone number
- *
- * @param allowOverride whether to use the override number in app settings
- * @return the canonicalized self phone number
- */
- public String getCanonicalForSelf(final boolean allowOverride) {
- String selfNumber = null;
- try {
- selfNumber = getSelfRawNumber(allowOverride);
- } catch (IllegalStateException e) {
- // continue;
- }
- if (selfNumber == null) {
- return "";
- }
- return getCanonicalBySimLocale(selfNumber);
- }
-
- /**
- * Get the SIM's phone number in NATIONAL format with only digits, used in sending
- * as LINE1NOCOUNTRYCODE macro in mms_config
- *
- * @return all digits national format number of the SIM
- */
- public String getSimNumberNoCountryCode() {
- String selfNumber = null;
- try {
- selfNumber = getSelfRawNumber(false/*allowOverride*/);
- } catch (IllegalStateException e) {
- // continue
- }
- if (selfNumber == null) {
- selfNumber = "";
- }
- final String country = getSimCountry();
- final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
- try {
- final PhoneNumber phoneNumber = phoneNumberUtil.parse(selfNumber, country);
- if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) {
- return phoneNumberUtil
- .format(phoneNumber, PhoneNumberFormat.NATIONAL)
- .replaceAll("\\D", "");
- }
- } catch (final NumberParseException e) {
- LogUtil.e(TAG, "PhoneUtils.getSimNumberNoCountryCode(): Not able to parse phone number "
- + LogUtil.sanitizePII(selfNumber) + " for country " + country);
- }
- return selfNumber;
-
- }
-
- /**
- * Format a phone number for displaying, using system locale country.
- * If the country code matches between the system locale and the input phone number,
- * it will be formatted into NATIONAL format, otherwise, the INTERNATIONAL format
- *
- * @param phoneText The original phone text
- * @return formatted number
- */
- public String formatForDisplay(final String phoneText) {
- // Only format a valid number which length >=6
- if (TextUtils.isEmpty(phoneText) ||
- phoneText.replaceAll("\\D", "").length() < MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT) {
- return phoneText;
- }
- final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
- final String systemCountry = getLocaleCountry();
- final int systemCountryCode = phoneNumberUtil.getCountryCodeForRegion(systemCountry);
- try {
- final PhoneNumber parsedNumber = phoneNumberUtil.parse(phoneText, systemCountry);
- final PhoneNumberFormat phoneNumberFormat =
- (systemCountryCode > 0 && parsedNumber.getCountryCode() == systemCountryCode) ?
- PhoneNumberFormat.NATIONAL : PhoneNumberFormat.INTERNATIONAL;
- return phoneNumberUtil.format(parsedNumber, phoneNumberFormat);
- } catch (NumberParseException e) {
- LogUtil.e(TAG, "PhoneUtils.formatForDisplay: invalid phone number "
- + LogUtil.sanitizePII(phoneText) + " with country " + systemCountry);
- return phoneText;
- }
- }
-
- /**
- * Is Messaging the default SMS app?
- * - On KLP+ this checks the system setting.
- * - On JB (and below) this always returns true, since the setting was added in KLP.
- */
- public boolean isDefaultSmsApp() {
- if (OsUtil.isAtLeastKLP()) {
- final String configuredApplication = Telephony.Sms.getDefaultSmsPackage(mContext);
- return mContext.getPackageName().equals(configuredApplication);
- }
- return true;
- }
-
- /**
- * Get default SMS app package name
- *
- * @return the package name of default SMS app
- */
- public String getDefaultSmsApp() {
- if (OsUtil.isAtLeastKLP()) {
- return Telephony.Sms.getDefaultSmsPackage(mContext);
- }
- return null;
- }
-
- /**
- * Determines if SMS is currently enabled on this device.
- * - Device must support SMS
- * - On KLP+ we must be set as the default SMS app
- */
- public boolean isSmsEnabled() {
- return isSmsCapable() && isDefaultSmsApp();
- }
-
- /**
- * Returns the name of the default SMS app, or the empty string if there is
- * an error or there is no default app (e.g. JB and below).
- */
- public String getDefaultSmsAppLabel() {
- if (OsUtil.isAtLeastKLP()) {
- final String packageName = Telephony.Sms.getDefaultSmsPackage(mContext);
- final PackageManager pm = mContext.getPackageManager();
- try {
- final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
- return pm.getApplicationLabel(appInfo).toString();
- } catch (NameNotFoundException e) {
- // Fall through and return empty string
- }
- }
- return "";
- }
-
- /**
- * Gets the state of Airplane Mode.
- *
- * @return true if enabled.
- */
- @SuppressWarnings("deprecation")
- public boolean isAirplaneModeOn() {
- if (OsUtil.isAtLeastJB_MR1()) {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
- } else {
- return Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.AIRPLANE_MODE_ON, 0) != 0;
- }
- }
-
- public static String getMccMncString(int[] mccmnc) {
- if (mccmnc == null || mccmnc.length != 2) {
- return "000000";
- }
- return String.format("%03d%03d", mccmnc[0], mccmnc[1]);
- }
-
- public static String canonicalizeMccMnc(final String mcc, final String mnc) {
- try {
- return String.format("%03d%03d", Integer.parseInt(mcc), Integer.parseInt(mnc));
- } catch (final NumberFormatException e) {
- // Return invalid as is
- LogUtil.w(TAG, "canonicalizeMccMnc: invalid mccmnc:" + mcc + " ," + mnc);
- }
- return mcc + mnc;
- }
-
- /**
- * Returns whether the given destination is valid for sending SMS/MMS message.
- */
- public static boolean isValidSmsMmsDestination(final String destination) {
- return PhoneNumberUtils.isWellFormedSmsAddress(destination) ||
- MmsSmsUtils.isEmailAddress(destination);
- }
-
- public interface SubscriptionRunnable {
- void runForSubscription(int subId);
- }
-
- /**
- * A convenience method for iterating through all active subscriptions
- *
- * @param runnable a {@link SubscriptionRunnable} for performing work on each subscription.
- */
- public static void forEachActiveSubscription(final SubscriptionRunnable runnable) {
- if (OsUtil.isAtLeastL_MR1()) {
- final List<SubscriptionInfo> subscriptionList =
- getDefault().toLMr1().getActiveSubscriptionInfoList();
- for (final SubscriptionInfo subscriptionInfo : subscriptionList) {
- runnable.runForSubscription(subscriptionInfo.getSubscriptionId());
- }
- } else {
- runnable.runForSubscription(ParticipantData.DEFAULT_SELF_SUB_ID);
- }
- }
-
- private static String getNumberFromPrefs(final Context context, final int subId) {
- final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
- final String mmsPhoneNumberPrefKey =
- context.getString(R.string.mms_phone_number_pref_key);
- final String userDefinedNumber = prefs.getString(mmsPhoneNumberPrefKey, null);
- if (!TextUtils.isEmpty(userDefinedNumber)) {
- return userDefinedNumber;
- }
- return null;
- }
-}
diff --git a/src/com/android/messaging/util/RingtoneUtil.java b/src/com/android/messaging/util/RingtoneUtil.java
deleted file mode 100644
index a7facfb..0000000
--- a/src/com/android/messaging/util/RingtoneUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.net.Uri;
-import android.provider.Settings;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-
-public class RingtoneUtil {
- /**
- * Return a ringtone Uri for the string representation passed in. Use the app
- * and system defaults as fallbacks
- * @param ringtoneString is the ringtone to resolve
- * @return the Uri of the ringtone or the fallback ringtone
- */
- public static Uri getNotificationRingtoneUri(String ringtoneString) {
- if (ringtoneString == null) {
- // No override specified, fall back to system-wide setting.
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- final Context context = Factory.get().getApplicationContext();
- final String prefKey = context.getString(R.string.notification_sound_pref_key);
- ringtoneString = prefs.getString(prefKey, null);
- }
-
- if (!TextUtils.isEmpty(ringtoneString)) {
- // We have set a value, even if it is the default Uri at some point
- return Uri.parse(ringtoneString);
- } else if (ringtoneString == null) {
- // We have no setting specified (== null), so we default to the system default
- return Settings.System.DEFAULT_NOTIFICATION_URI;
- } else {
- // An empty string (== "") here is the result of selecting "None" as the ringtone
- return null;
- }
- }
-}
diff --git a/src/com/android/messaging/util/SafeAsyncTask.java b/src/com/android/messaging/util/SafeAsyncTask.java
deleted file mode 100644
index 1cce6e9..0000000
--- a/src/com/android/messaging/util/SafeAsyncTask.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Debug;
-import android.os.SystemClock;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.Assert.RunsOnAnyThread;
-
-/**
- * Wrapper class which provides explicit API for:
- * <ol>
- * <li>Threading policy choice - Users of this class should use the explicit API instead of
- * {@link #execute} which uses different threading policy on different OS versions.
- * <li>Enforce creation on main thread as required by AsyncTask
- * <li>Enforce that the background task does not take longer than expected.
- * </ol>
- */
-public abstract class SafeAsyncTask<Params, Progress, Result>
- extends AsyncTask<Params, Progress, Result> {
- private static final long DEFAULT_MAX_EXECUTION_TIME_MILLIS = 10 * 1000; // 10 seconds
-
- /** This is strongly discouraged as it can block other AsyncTasks indefinitely. */
- public static final long UNBOUNDED_TIME = Long.MAX_VALUE;
-
- private static final String WAKELOCK_ID = "bugle_safe_async_task_wakelock";
- protected static final int WAKELOCK_OP = 1000;
- private static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID);
-
- private final long mMaxExecutionTimeMillis;
- private final boolean mCancelExecutionOnTimeout;
- private boolean mThreadPoolRequested;
-
- public SafeAsyncTask() {
- this(DEFAULT_MAX_EXECUTION_TIME_MILLIS, false);
- }
-
- public SafeAsyncTask(final long maxTimeMillis) {
- this(maxTimeMillis, false);
- }
-
- /**
- * @param maxTimeMillis maximum expected time for the background operation. This is just
- * a diagnostic tool to catch unexpectedly long operations. If an operation does take
- * longer than expected, it is fine to increase this argument. If the value is larger
- * than a minute, you should consider using a dedicated thread so as not to interfere
- * with other AsyncTasks.
- *
- * <p>Use {@link #UNBOUNDED_TIME} if you do not know the maximum expected time. This
- * is strongly discouraged as it can block other AsyncTasks indefinitely.
- *
- * @param cancelExecutionOnTimeout whether to attempt to cancel the task execution on timeout.
- * If this is set, at execution timeout we will call cancel(), so doInBackgroundTimed()
- * should periodically check if the task is to be cancelled and finish promptly if
- * possible, and handle the cancel event in onCancelled(). Also, at the end of execution
- * we will not crash the execution if it went over limit since we explicitly canceled it.
- */
- public SafeAsyncTask(final long maxTimeMillis, final boolean cancelExecutionOnTimeout) {
- Assert.isMainThread(); // AsyncTask has to be created on the main thread
- mMaxExecutionTimeMillis = maxTimeMillis;
- mCancelExecutionOnTimeout = cancelExecutionOnTimeout;
- }
-
- public final SafeAsyncTask<Params, Progress, Result> executeOnThreadPool(
- final Params... params) {
- Assert.isMainThread(); // AsyncTask requires this
- mThreadPoolRequested = true;
- executeOnExecutor(THREAD_POOL_EXECUTOR, params);
- return this;
- }
-
- protected abstract Result doInBackgroundTimed(final Params... params);
-
- @Override
- protected final Result doInBackground(final Params... params) {
- // This enforces that executeOnThreadPool was called, not execute. Ideally, we would
- // make execute throw an exception, but since it is final, we cannot override it.
- Assert.isTrue(mThreadPoolRequested);
-
- if (mCancelExecutionOnTimeout) {
- ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- if (getStatus() == Status.RUNNING) {
- // Cancel the task if it's still running.
- LogUtil.w(LogUtil.BUGLE_TAG, String.format("%s timed out and is canceled",
- this));
- cancel(true /* mayInterruptIfRunning */);
- }
- }
- }, mMaxExecutionTimeMillis);
- }
-
- final long startTime = SystemClock.elapsedRealtime();
- try {
- return doInBackgroundTimed(params);
- } finally {
- final long executionTime = SystemClock.elapsedRealtime() - startTime;
- if (executionTime > mMaxExecutionTimeMillis) {
- LogUtil.w(LogUtil.BUGLE_TAG, String.format("%s took %dms", this, executionTime));
- // Don't crash if debugger is attached or if we are asked to cancel on timeout.
- if (!Debug.isDebuggerConnected() && !mCancelExecutionOnTimeout) {
- Assert.fail(this + " took too long");
- }
- }
- }
-
- }
-
- @Override
- protected void onPostExecute(final Result result) {
- // No need to use AsyncTask at all if there is no onPostExecute
- Assert.fail("Use SafeAsyncTask.executeOnThreadPool");
- }
-
- /**
- * This provides a way for people to run async tasks but without onPostExecute.
- * This can be called on any thread.
- *
- * Run code in a thread using AsyncTask's thread pool.
- *
- * To enable wakelock during the execution, see {@link #executeOnThreadPool(Runnable, boolean)}
- *
- * @param runnable The Runnable to execute asynchronously
- */
- @RunsOnAnyThread
- public static void executeOnThreadPool(final Runnable runnable) {
- executeOnThreadPool(runnable, false);
- }
-
- /**
- * This provides a way for people to run async tasks but without onPostExecute.
- * This can be called on any thread.
- *
- * Run code in a thread using AsyncTask's thread pool.
- *
- * @param runnable The Runnable to execute asynchronously
- * @param withWakeLock when set, a wake lock will be held for the duration of the runnable
- * execution
- */
- public static void executeOnThreadPool(final Runnable runnable, final boolean withWakeLock) {
- if (withWakeLock) {
- final Intent intent = new Intent();
- sWakeLock.acquire(Factory.get().getApplicationContext(), intent, WAKELOCK_OP);
- THREAD_POOL_EXECUTOR.execute(new Runnable() {
- @Override
- public void run() {
- try {
- runnable.run();
- } finally {
- sWakeLock.release(intent, WAKELOCK_OP);
- }
- }
- });
- } else {
- THREAD_POOL_EXECUTOR.execute(runnable);
- }
- }
-}
diff --git a/src/com/android/messaging/util/SwitchCompatUtils.java b/src/com/android/messaging/util/SwitchCompatUtils.java
deleted file mode 100644
index b5d1ed5..0000000
--- a/src/com/android/messaging/util/SwitchCompatUtils.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.support.v7.graphics.drawable.DrawableWrapper;
-import android.support.v7.widget.SwitchCompat;
-import android.util.TypedValue;
-
-/* Most methods in this file are copied from
- * v7/appcompat/src/android/support/v7/internal/widget/TintManager.java. It would be better if
- * we could have just extended the TintManager but this is a final class that we do not have
- * access to. */
-
-/**
- * Util methods for the SwitchCompat widget
- */
-public class SwitchCompatUtils {
- /**
- * Given a color and a SwitchCompat view, updates the SwitchCompat to appear with the appropiate
- * color when enabled and checked
- */
- public static void updateSwitchCompatColor(SwitchCompat switchCompat, final int color) {
- final Context context = switchCompat.getContext();
- final TypedValue typedValue = new TypedValue();
-
- switchCompat.setThumbDrawable(getColorTintedDrawable(switchCompat.getThumbDrawable(),
- getSwitchThumbColorStateList(context, color, typedValue),
- PorterDuff.Mode.MULTIPLY));
-
- switchCompat.setTrackDrawable(getColorTintedDrawable(switchCompat.getTrackDrawable(),
- getSwitchTrackColorStateList(context, color, typedValue), PorterDuff.Mode.SRC_IN));
- }
-
- private static Drawable getColorTintedDrawable(Drawable oldDrawable,
- final ColorStateList colorStateList, final PorterDuff.Mode mode) {
- final int[] thumbState = oldDrawable.isStateful() ? oldDrawable.getState() : null;
- if (oldDrawable instanceof DrawableWrapper) {
- oldDrawable = ((DrawableWrapper) oldDrawable).getWrappedDrawable();
- }
- final Drawable newDrawable = new TintDrawableWrapper(oldDrawable, colorStateList, mode);
- if (thumbState != null) {
- newDrawable.setState(thumbState);
- }
- return newDrawable;
- }
-
- private static ColorStateList getSwitchThumbColorStateList(final Context context,
- final int color, final TypedValue typedValue) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
- // Disabled state
- states[i] = new int[] { -android.R.attr.state_enabled };
- colors[i] = getColor(Color.parseColor("#ffbdbdbd"), 1f);
- i++;
- states[i] = new int[] { android.R.attr.state_checked };
- colors[i] = color;
- i++;
- // Default enabled state
- states[i] = new int[0];
- colors[i] = getThemeAttrColor(context, typedValue,
- android.support.v7.appcompat.R.attr.colorSwitchThumbNormal);
- i++;
- return new ColorStateList(states, colors);
- }
-
- private static ColorStateList getSwitchTrackColorStateList(final Context context,
- final int color, final TypedValue typedValue) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
- // Disabled state
- states[i] = new int[] { -android.R.attr.state_enabled };
- colors[i] = getThemeAttrColor(context, typedValue, android.R.attr.colorForeground, 0.1f);
- i++;
- states[i] = new int[] { android.R.attr.state_checked };
- colors[i] = getColor(color, 0.3f);
- i++;
- // Default enabled state
- states[i] = new int[0];
- colors[i] = getThemeAttrColor(context, typedValue, android.R.attr.colorForeground, 0.3f);
- i++;
- return new ColorStateList(states, colors);
- }
-
- private static int getThemeAttrColor(final Context context, final TypedValue typedValue,
- final int attr) {
- if (context.getTheme().resolveAttribute(attr, typedValue, true)) {
- if (typedValue.type >= TypedValue.TYPE_FIRST_INT
- && typedValue.type <= TypedValue.TYPE_LAST_INT) {
- return typedValue.data;
- } else if (typedValue.type == TypedValue.TYPE_STRING) {
- return context.getResources().getColor(typedValue.resourceId);
- }
- }
- return 0;
- }
-
- private static int getThemeAttrColor(final Context context, final TypedValue typedValue,
- final int attr, final float alpha) {
- final int color = getThemeAttrColor(context, typedValue, attr);
- return getColor(color, alpha);
- }
-
- private static int getColor(int color, float alpha) {
- final int originalAlpha = Color.alpha(color);
- // Return the color, multiplying the original alpha by the disabled value
- return (color & 0x00ffffff) | (Math.round(originalAlpha * alpha) << 24);
- }
-}
diff --git a/src/com/android/messaging/util/TextUtil.java b/src/com/android/messaging/util/TextUtil.java
deleted file mode 100644
index b240396..0000000
--- a/src/com/android/messaging/util/TextUtil.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.support.annotation.Nullable;
-
-public class TextUtil {
- /**
- * Returns true if the string is empty, null or only whitespace.
- */
- public static boolean isAllWhitespace(@Nullable String string) {
- if (string == null || string.isEmpty()) {
- return true;
- }
-
- for (int i = 0; i < string.length(); ++i) {
- if (!Character.isWhitespace(string.charAt(i))) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Taken from PhoneNumberUtils, where it is only available in API 21+ Replaces all unicode
- * (e.g. Arabic, Persian) digits with their decimal digit equivalents.
- *
- * @param number the number to perform the replacement on.
- * @return the replaced number.
- */
- public static String replaceUnicodeDigits(String number) {
- StringBuilder normalizedDigits = new StringBuilder(number.length());
- for (char c : number.toCharArray()) {
- int digit = Character.digit(c, 10);
- if (digit != -1) {
- normalizedDigits.append(digit);
- } else {
- normalizedDigits.append(c);
- }
- }
- return normalizedDigits.toString();
- }
-
- /**
- * Appends text to the stringBuilder.
- * If stringBuilder already has content, separator is prepended to create a separator between
- * entries.
- * @param stringBuilder The stringBuilder to add to
- * @param text The text to append
- * @param separator The separator to add if there is already text, typically "," or "\n"
- */
- public static void appendWithSeparator(final StringBuilder stringBuilder, final String text,
- final String separator) {
- if (stringBuilder.length() > 0) {
- stringBuilder.append(separator);
- }
- stringBuilder.append(text);
- }
-}
diff --git a/src/com/android/messaging/util/ThreadUtil.java b/src/com/android/messaging/util/ThreadUtil.java
deleted file mode 100644
index 3b935d8..0000000
--- a/src/com/android/messaging/util/ThreadUtil.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.os.Handler;
-import android.os.Looper;
-
-public class ThreadUtil {
- private static final Handler sHandler = new Handler(Looper.getMainLooper());
-
- public static Handler getMainThreadHandler() {
- return sHandler;
- }
-}
diff --git a/src/com/android/messaging/util/TintDrawableWrapper.java b/src/com/android/messaging/util/TintDrawableWrapper.java
deleted file mode 100644
index e6ea4bd..0000000
--- a/src/com/android/messaging/util/TintDrawableWrapper.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.support.v7.graphics.drawable.DrawableWrapper;
-
-/*
- * This is directly copied from v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
- */
-
-/**
- * A {@link DrawableWrapper} which updates it's color filter using a {@link ColorStateList}.
- */
-class TintDrawableWrapper extends DrawableWrapper {
- private final ColorStateList mTintStateList;
- private final PorterDuff.Mode mTintMode;
- private int mCurrentColor;
- public TintDrawableWrapper(Drawable drawable, ColorStateList tintStateList) {
- this(drawable, tintStateList, PorterDuff.Mode.SRC_IN);
- }
- public TintDrawableWrapper(Drawable drawable, ColorStateList tintStateList,
- PorterDuff.Mode tintMode) {
- super(drawable);
- mTintStateList = tintStateList;
- mTintMode = tintMode;
- }
- @Override
- public boolean isStateful() {
- return (mTintStateList != null && mTintStateList.isStateful()) || super.isStateful();
- }
- @Override
- public boolean setState(int[] stateSet) {
- boolean handled = super.setState(stateSet);
- handled = updateTint(stateSet) || handled;
- return handled;
- }
- private boolean updateTint(int[] state) {
- if (mTintStateList != null) {
- final int color = mTintStateList.getColorForState(state, mCurrentColor);
- if (color != mCurrentColor) {
- if (color != Color.TRANSPARENT) {
- setColorFilter(color, mTintMode);
- } else {
- clearColorFilter();
- }
- mCurrentColor = color;
- return true;
- }
- }
- return false;
- }
-}
diff --git a/src/com/android/messaging/util/Trace.java b/src/com/android/messaging/util/Trace.java
deleted file mode 100644
index da1e87c..0000000
--- a/src/com/android/messaging/util/Trace.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-
-import android.annotation.TargetApi;
-import android.os.Build;
-
-/**
- * Helper class for systrace (see http://developer.android.com/tools/help/systrace.html).<p>
- * To enable, set log.tag.Bugle_Trace (defined by {@link #TAG} to VERBOSE before
- * the process starts.<p>
- * Note that this will run only on JBMR2 or later; on earlier platforms or if the log
- * tag isn't set, calls to {@link #beginSection(String)} or {@link #endSection()} are no-ops. <p>
- * Internally, calls dispatch to either a class that actually does work or a class that doesn't.
- * This avoids Dalvik complaining when it loads the class on earlier platforms that the
- * opcodes aren't available, and, according to the Dalvik team, using vtable dispatching for
- * something like this should be faster than if (OsUtil.isAtLeast...()) on each call.
- */
-public final class Trace {
- private static final String TAG = "Bugle_Trace";
- private abstract static class AbstractTrace {
- abstract void beginSection(String sectionName);
- abstract void endSection();
- }
-
- private static final AbstractTrace sTrace;
-
- // Static initializer to pick the correct trace class to handle tracing.
- static {
- // Use android.util.Log instead of LogUtil here to avoid pulling in Gservices
- // too early in app startup.
- if (OsUtil.isAtLeastJB_MR2() &&
- android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE)) {
- sTrace = new TraceJBMR2();
- } else {
- sTrace = new TraceShim();
- }
- }
-
- /**
- * Writes a trace message to indicate that a given section of code has begun. This call must
- * be followed by a corresponding call to {@link #endSection()} on the same thread.
- *
- * <p class="note"> At this time the vertical bar character '|', newline character '\n', and
- * null character '\0' are used internally by the tracing mechanism. If sectionName contains
- * these characters they will be replaced with a space character in the trace.
- *
- * @param sectionName The name of the code section to appear in the trace. This may be at
- * most 127 Unicode code units long.
- */
- public static void beginSection(String sectionName) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "beginSection() " + sectionName);
- }
- sTrace.beginSection(sectionName);
- }
-
- /**
- * Writes a trace message to indicate that a given section of code has ended. This call must
- * be preceeded by a corresponding call to {@link #beginSection(String)}. Calling this method
- * will mark the end of the most recently begun section of code, so care must be taken to
- * ensure that beginSection / endSection pairs are properly nested and called from the same
- * thread.
- */
- public static void endSection() {
- sTrace.endSection();
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "endSection()");
- }
- }
-
- /**
- * Internal class that we use if we really did enable tracing.
- */
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
- private static final class TraceJBMR2 extends AbstractTrace {
- @Override
- void beginSection(String sectionName) {
- android.os.Trace.beginSection(sectionName);
- }
-
- @Override
- void endSection() {
- android.os.Trace.endSection();
- }
- }
-
- /**
- * Dummy class that we use if we aren't really tracing.
- */
- private static final class TraceShim extends AbstractTrace {
- @Override
- void beginSection(String sectionName) {
- }
-
- @Override
- void endSection() {
- }
- }
-}
diff --git a/src/com/android/messaging/util/Typefaces.java b/src/com/android/messaging/util/Typefaces.java
deleted file mode 100644
index eb8562c..0000000
--- a/src/com/android/messaging/util/Typefaces.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.graphics.Typeface;
-
-/**
- * Provides access to typefaces used by code. Specially important for typefaces coming from assets,
- * which appear (from platform code inspection) to not be cached.
- * Note: Considered making this a singleton provided by factory/appcontext, but seemed too simple,
- * not worth stubbing.
- */
-public class Typefaces {
- private static Typeface sRobotoBold;
- private static Typeface sRobotoNormal;
-
- public static Typeface getRobotoBold() {
- Assert.isMainThread();
- if (sRobotoBold == null) {
- sRobotoBold = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
- }
- return sRobotoBold;
- }
-
- public static Typeface getRobotoNormal() {
- Assert.isMainThread();
- if (sRobotoNormal == null) {
- sRobotoNormal = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
- }
- return sRobotoNormal;
- }
-}
diff --git a/src/com/android/messaging/util/UiUtils.java b/src/com/android/messaging/util/UiUtils.java
deleted file mode 100644
index 84fe353..0000000
--- a/src/com/android/messaging/util/UiUtils.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarActivity;
-import android.text.Html;
-import android.text.Spanned;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.style.URLSpan;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
-import android.view.animation.Interpolator;
-import android.view.animation.ScaleAnimation;
-import android.widget.RemoteViews;
-import android.widget.Toast;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.ui.SnackBar;
-import com.android.messaging.ui.SnackBar.Placement;
-import com.android.messaging.ui.conversationlist.ConversationListActivity;
-import com.android.messaging.ui.SnackBarInteraction;
-import com.android.messaging.ui.SnackBarManager;
-import com.android.messaging.ui.UIIntents;
-
-import java.lang.reflect.Field;
-import java.util.List;
-
-public class UiUtils {
- /** MediaPicker transition duration in ms */
- public static final int MEDIAPICKER_TRANSITION_DURATION =
- getApplicationContext().getResources().getInteger(
- R.integer.mediapicker_transition_duration);
- /** Short transition duration in ms */
- public static final int ASYNCIMAGE_TRANSITION_DURATION =
- getApplicationContext().getResources().getInteger(
- R.integer.asyncimage_transition_duration);
- /** Compose transition duration in ms */
- public static final int COMPOSE_TRANSITION_DURATION =
- getApplicationContext().getResources().getInteger(
- R.integer.compose_transition_duration);
- /** Generic duration for revealing/hiding a view */
- public static final int REVEAL_ANIMATION_DURATION =
- getApplicationContext().getResources().getInteger(
- R.integer.reveal_view_animation_duration);
-
- public static final Interpolator DEFAULT_INTERPOLATOR = new CubicBezierInterpolator(
- 0.4f, 0.0f, 0.2f, 1.0f);
-
- public static final Interpolator EASE_IN_INTERPOLATOR = new CubicBezierInterpolator(
- 0.4f, 0.0f, 0.8f, 0.5f);
-
- public static final Interpolator EASE_OUT_INTERPOLATOR = new CubicBezierInterpolator(
- 0.0f, 0.0f, 0.2f, 1f);
-
- /** Show a simple toast at the bottom */
- public static void showToastAtBottom(final int messageId) {
- UiUtils.showToastAtBottom(getApplicationContext().getString(messageId));
- }
-
- /** Show a simple toast at the bottom */
- public static void showToastAtBottom(final String message) {
- final Toast toast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG);
- toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
- toast.show();
- }
-
- /** Show a simple toast at the default position */
- public static void showToast(final int messageId) {
- final Toast toast = Toast.makeText(getApplicationContext(),
- getApplicationContext().getString(messageId), Toast.LENGTH_LONG);
- toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0);
- toast.show();
- }
-
- /** Show a simple toast at the default position */
- public static void showToast(final int pluralsMessageId, final int count) {
- final Toast toast = Toast.makeText(getApplicationContext(),
- getApplicationContext().getResources().getQuantityString(pluralsMessageId, count),
- Toast.LENGTH_LONG);
- toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0);
- toast.show();
- }
-
- public static void showSnackBar(final Context context, @NonNull final View parentView,
- final String message, @Nullable final Runnable runnable, final int runnableLabel,
- @Nullable final List<SnackBarInteraction> interactions) {
- Assert.notNull(context);
- SnackBar.Action action = null;
- switch (runnableLabel) {
- case SnackBar.Action.SNACK_BAR_UNDO:
- action = SnackBar.Action.createUndoAction(runnable);
- break;
- case SnackBar.Action.SNACK_BAR_RETRY:
- action = SnackBar.Action.createRetryAction(runnable);
- break;
- default :
- break;
- }
-
- showSnackBarWithCustomAction(context, parentView, message, action, interactions,
- null /* placement */);
- }
-
- public static void showSnackBarWithCustomAction(final Context context,
- @NonNull final View parentView,
- @NonNull final String message,
- @NonNull final SnackBar.Action action,
- @Nullable final List<SnackBarInteraction> interactions,
- @Nullable final Placement placement) {
- Assert.notNull(context);
- Assert.isTrue(!TextUtils.isEmpty(message));
- Assert.notNull(action);
- SnackBarManager.get()
- .newBuilder(parentView)
- .setText(message)
- .setAction(action)
- .withInteractions(interactions)
- .withPlacement(placement)
- .show();
- }
-
- /**
- * Run the given runnable once after the next layout pass of the view.
- */
- public static void doOnceAfterLayoutChange(final View view, final Runnable runnable) {
- final OnLayoutChangeListener listener = new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- // Call the runnable outside the layout pass because very few actions are allowed in
- // the layout pass
- ThreadUtil.getMainThreadHandler().post(runnable);
- view.removeOnLayoutChangeListener(this);
- }
- };
- view.addOnLayoutChangeListener(listener);
- }
-
- public static boolean isLandscapeMode() {
- return Factory.get().getApplicationContext().getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
- }
-
- private static Context getApplicationContext() {
- return Factory.get().getApplicationContext();
- }
-
- public static CharSequence commaEllipsize(
- final String text,
- final TextPaint paint,
- final int width,
- final String oneMore,
- final String more) {
- CharSequence ellipsized = TextUtils.commaEllipsize(
- text,
- paint,
- width,
- oneMore,
- more);
- if (TextUtils.isEmpty(ellipsized)) {
- ellipsized = text;
- }
- return ellipsized;
- }
-
- /**
- * Reveals/Hides a view with a scale animation from view center.
- * @param view the view to animate
- * @param desiredVisibility desired visibility (e.g. View.GONE) for the animated view.
- * @param onFinishRunnable an optional runnable called at the end of the animation
- */
- public static void revealOrHideViewWithAnimation(final View view, final int desiredVisibility,
- @Nullable final Runnable onFinishRunnable) {
- final boolean needAnimation = view.getVisibility() != desiredVisibility;
- if (needAnimation) {
- final float fromScale = desiredVisibility == View.VISIBLE ? 0F : 1F;
- final float toScale = desiredVisibility == View.VISIBLE ? 1F : 0F;
- final ScaleAnimation showHideAnimation =
- new ScaleAnimation(fromScale, toScale, fromScale, toScale,
- ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
- ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
- showHideAnimation.setDuration(REVEAL_ANIMATION_DURATION);
- showHideAnimation.setInterpolator(DEFAULT_INTERPOLATOR);
- showHideAnimation.setAnimationListener(new AnimationListener() {
- @Override
- public void onAnimationStart(final Animation animation) {
- }
-
- @Override
- public void onAnimationRepeat(final Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(final Animation animation) {
- if (onFinishRunnable != null) {
- // Rather than running this immediately, we post it to happen next so that
- // the animation will be completed so that the view can be detached from
- // it's window. Otherwise, we may leak memory.
- ThreadUtil.getMainThreadHandler().post(onFinishRunnable);
- }
- }
- });
- view.clearAnimation();
- view.startAnimation(showHideAnimation);
- // We are playing a view Animation; unlike view property animations, we can commit the
- // visibility immediately instead of waiting for animation end.
- view.setVisibility(desiredVisibility);
- } else if (onFinishRunnable != null) {
- // Make sure onFinishRunnable is always executed.
- ThreadUtil.getMainThreadHandler().post(onFinishRunnable);
- }
- }
-
- public static Rect getMeasuredBoundsOnScreen(final View view) {
- final int[] location = new int[2];
- view.getLocationOnScreen(location);
- return new Rect(location[0], location[1],
- location[0] + view.getMeasuredWidth(), location[1] + view.getMeasuredHeight());
- }
-
- public static void setStatusBarColor(final Activity activity, final int color) {
- if (OsUtil.isAtLeastL()) {
- // To achieve the appearance of an 80% opacity blend against a black background,
- // each color channel is reduced in value by 20%.
- final int blendedRed = (int) Math.floor(0.8 * Color.red(color));
- final int blendedGreen = (int) Math.floor(0.8 * Color.green(color));
- final int blendedBlue = (int) Math.floor(0.8 * Color.blue(color));
-
- activity.getWindow().setStatusBarColor(
- Color.rgb(blendedRed, blendedGreen, blendedBlue));
- }
- }
-
- public static void lockOrientation(final Activity activity) {
- final int orientation = activity.getResources().getConfiguration().orientation;
- final int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
-
- // rotation tracks the rotation of the device from its natural orientation
- // orientation tracks whether the screen is landscape or portrait.
- // It is possible to have a rotation of 0 (device in its natural orientation) in portrait
- // (phone), or in landscape (tablet), so we have to check both values to determine what to
- // pass to setRequestedOrientation.
- if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- }
- } else if (rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270) {
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
- } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- }
- }
- }
-
- public static void unlockOrientation(final Activity activity) {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
- }
-
- public static int getPaddingStart(final View view) {
- return OsUtil.isAtLeastJB_MR1() ? view.getPaddingStart() : view.getPaddingLeft();
- }
-
- public static int getPaddingEnd(final View view) {
- return OsUtil.isAtLeastJB_MR1() ? view.getPaddingEnd() : view.getPaddingRight();
- }
-
- public static boolean isRtlMode() {
- return OsUtil.isAtLeastJB_MR2() && Factory.get().getApplicationContext().getResources()
- .getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
- }
-
- /**
- * Check if the activity needs to be redirected to permission check
- * @return true if {@link Activity#finish()} was called because redirection was performed
- */
- public static boolean redirectToPermissionCheckIfNeeded(final Activity activity) {
- if (!OsUtil.hasRequiredPermissions()) {
- UIIntents.get().launchPermissionCheckActivity(activity);
- } else {
- // No redirect performed
- return false;
- }
-
- // Redirect performed
- activity.finish();
- return true;
- }
-
- /**
- * Called to check if all conditions are nominal and a "go" for some action, such as deleting
- * a message, that requires this app to be the default app. This is also a precondition
- * required for sending a draft.
- * @return true if all conditions are nominal and we're ready to send a message
- */
- public static boolean isReadyForAction() {
- final PhoneUtils phoneUtils = PhoneUtils.getDefault();
-
- // Have all the conditions been met:
- // Supports SMS?
- // Has a preferred sim?
- // Is the default sms app?
- return phoneUtils.isSmsCapable() &&
- phoneUtils.getHasPreferredSmsSim() &&
- phoneUtils.isDefaultSmsApp();
- }
-
- /*
- * Removes all html markup from the text and replaces links with the the text and a text version
- * of the href.
- * @param htmlText HTML markup text
- * @return Sanitized string with link hrefs inlined
- */
- public static String stripHtml(final String htmlText) {
- final StringBuilder result = new StringBuilder();
- final Spanned markup = Html.fromHtml(htmlText);
- final String strippedText = markup.toString();
-
- final URLSpan[] links = markup.getSpans(0, markup.length() - 1, URLSpan.class);
- int currentIndex = 0;
- for (final URLSpan link : links) {
- final int spanStart = markup.getSpanStart(link);
- final int spanEnd = markup.getSpanEnd(link);
- if (spanStart > currentIndex) {
- result.append(strippedText, currentIndex, spanStart);
- }
- final String displayText = strippedText.substring(spanStart, spanEnd);
- final String linkText = link.getURL();
- result.append(getApplicationContext().getString(R.string.link_display_format,
- displayText, linkText));
- currentIndex = spanEnd;
- }
- if (strippedText.length() > currentIndex) {
- result.append(strippedText, currentIndex, strippedText.length());
- }
- return result.toString();
- }
-
- public static void setActionBarShadowVisibility(final ActionBarActivity activity, final boolean visible) {
- final ActionBar actionBar = activity.getSupportActionBar();
- actionBar.setElevation(visible ?
- activity.getResources().getDimensionPixelSize(R.dimen.action_bar_elevation) :
- 0);
- final View actionBarView = activity.getWindow().getDecorView().findViewById(
- android.support.v7.appcompat.R.id.decor_content_parent);
- if (actionBarView != null) {
- // AppCompatActionBar has one drawable Field, which is the shadow for the action bar
- // set the alpha on that drawable manually
- final Field[] fields = actionBarView.getClass().getDeclaredFields();
- try {
- for (final Field field : fields) {
- if (field.getType().equals(Drawable.class)) {
- field.setAccessible(true);
- final Drawable shadowDrawable = (Drawable) field.get(actionBarView);
- if (shadowDrawable != null) {
- shadowDrawable.setAlpha(visible ? 255 : 0);
- actionBarView.invalidate();
- return;
- }
- }
- }
- } catch (final IllegalAccessException ex) {
- // Not expected, we should avoid this via field.setAccessible(true) above
- LogUtil.e(LogUtil.BUGLE_TAG, "Error setting shadow visibility", ex);
- }
- }
- }
-
- /**
- * Get the activity that's hosting the view, typically casting view.getContext() as an Activity
- * is sufficient, but sometimes the context is a context wrapper, in which case we need to case
- * the base context
- */
- public static Activity getActivity(final View view) {
- if (view == null) {
- return null;
- }
- return getActivity(view.getContext());
- }
-
- /**
- * Get the activity for the supplied context, typically casting context as an Activity
- * is sufficient, but sometimes the context is a context wrapper, in which case we need to case
- * the base context
- */
- public static Activity getActivity(final Context context) {
- if (context == null) {
- return null;
- }
- if (context instanceof Activity) {
- return (Activity) context;
- }
- if (context instanceof ContextWrapper) {
- return getActivity(((ContextWrapper) context).getBaseContext());
- }
-
- // We've hit a non-activity context such as an app-context
- return null;
- }
-
- public static RemoteViews getWidgetMissingPermissionView(final Context context) {
- return new RemoteViews(context.getPackageName(), R.layout.widget_missing_permission);
- }
-}
diff --git a/src/com/android/messaging/util/UriUtil.java b/src/com/android/messaging/util/UriUtil.java
deleted file mode 100644
index 4bbc80d..0000000
--- a/src/com/android/messaging/util/UriUtil.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Files;
-import com.google.common.io.Resources;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Arrays;
-import java.util.HashSet;
-
-public class UriUtil {
- private static final String SCHEME_SMS = "sms";
- private static final String SCHEME_SMSTO = "smsto";
- private static final String SCHEME_MMS = "mms";
- private static final String SCHEME_MMSTO = "smsto";
- public static final HashSet<String> SMS_MMS_SCHEMES = new HashSet<String>(
- Arrays.asList(SCHEME_SMS, SCHEME_MMS, SCHEME_SMSTO, SCHEME_MMSTO));
-
- public static final String SCHEME_BUGLE = "bugle";
- public static final HashSet<String> SUPPORTED_SCHEME = new HashSet<String>(
- Arrays.asList(ContentResolver.SCHEME_ANDROID_RESOURCE,
- ContentResolver.SCHEME_CONTENT,
- ContentResolver.SCHEME_FILE,
- SCHEME_BUGLE));
-
- public static final String SCHEME_TEL = "tel:";
-
- /**
- * Get a Uri representation of the file path of a resource file.
- */
- public static Uri getUriForResourceFile(final String path) {
- return TextUtils.isEmpty(path) ? null : Uri.fromFile(new File(path));
- }
-
- /**
- * Extract the path from a file:// Uri, or null if the uri is of other scheme.
- */
- public static String getFilePathFromUri(final Uri uri) {
- if (!isFileUri(uri)) {
- return null;
- }
- return uri.getPath();
- }
-
- /**
- * Returns whether the given Uri is local or remote.
- */
- public static boolean isLocalResourceUri(final Uri uri) {
- final String scheme = uri.getScheme();
- return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE) ||
- TextUtils.equals(scheme, ContentResolver.SCHEME_CONTENT) ||
- TextUtils.equals(scheme, ContentResolver.SCHEME_FILE);
- }
-
- /**
- * Returns whether the given Uri is part of Bugle's app package
- */
- public static boolean isBugleAppResource(final Uri uri) {
- final String scheme = uri.getScheme();
- return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE);
- }
-
- public static boolean isFileUri(final Uri uri) {
- return uri != null && TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE);
- }
-
- /**
- * Constructs an android.resource:// uri for the given resource id.
- */
- public static Uri getUriForResourceId(final Context context, final int resId) {
- return new Uri.Builder()
- .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
- .authority(context.getPackageName())
- .appendPath(String.valueOf(resId))
- .build();
- }
-
- /**
- * Returns whether the given Uri string is local.
- */
- public static boolean isLocalUri(@NonNull final Uri uri) {
- Assert.notNull(uri);
- return SUPPORTED_SCHEME.contains(uri.getScheme());
- }
-
- private static final String MEDIA_STORE_URI_KLP = "com.android.providers.media.documents";
-
- /**
- * Check if a URI is from the MediaStore
- */
- public static boolean isMediaStoreUri(final Uri uri) {
- final String uriAuthority = uri.getAuthority();
- return TextUtils.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())
- && (TextUtils.equals(MediaStore.AUTHORITY, uriAuthority) ||
- // KK changed the media store authority name
- TextUtils.equals(MEDIA_STORE_URI_KLP, uriAuthority));
- }
-
- /**
- * Gets the size in bytes for the content uri. Currently we only support content in the
- * scratch space.
- */
- @DoesNotRunOnMainThread
- public static long getContentSize(final Uri uri) {
- Assert.isNotMainThread();
- if (isLocalResourceUri(uri)) {
- ParcelFileDescriptor pfd = null;
- try {
- pfd = Factory.get().getApplicationContext()
- .getContentResolver().openFileDescriptor(uri, "r");
- return Math.max(pfd.getStatSize(), 0);
- } catch (final FileNotFoundException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error getting content size", e);
- } finally {
- if (pfd != null) {
- try {
- pfd.close();
- } catch (final IOException e) {
- // Do nothing.
- }
- }
- }
- } else {
- Assert.fail("Unsupported uri type!");
- }
- return 0;
- }
-
- /** @return duration in milliseconds or 0 if not able to determine */
- public static int getMediaDurationMs(final Uri uri) {
- final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
- try {
- retriever.setDataSource(uri);
- return retriever.extractInteger(MediaMetadataRetriever.METADATA_KEY_DURATION, 0);
- } catch (final IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Unable extract duration from media file: " + uri, e);
- return 0;
- } finally {
- retriever.release();
- }
- }
-
- /**
- * Persist a piece of content from the given input stream, byte by byte to the scratch
- * directory.
- * @return the output Uri if the operation succeeded, or null if failed.
- */
- @DoesNotRunOnMainThread
- public static Uri persistContentToScratchSpace(final InputStream inputStream) {
- final Context context = Factory.get().getApplicationContext();
- final Uri scratchSpaceUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
- return copyContent(context, inputStream, scratchSpaceUri);
- }
-
- /**
- * Persist a piece of content from the given sourceUri, byte by byte to the scratch
- * directory.
- * @return the output Uri if the operation succeeded, or null if failed.
- */
- @DoesNotRunOnMainThread
- public static Uri persistContentToScratchSpace(final Uri sourceUri) {
- InputStream inputStream = null;
- final Context context = Factory.get().getApplicationContext();
- try {
- if (UriUtil.isLocalResourceUri(sourceUri)) {
- inputStream = context.getContentResolver().openInputStream(sourceUri);
- } else {
- // The content is remote. Download it.
- final URL url = new URL(sourceUri.toString());
- final URLConnection ucon = url.openConnection();
- inputStream = new BufferedInputStream(ucon.getInputStream());
- }
- return persistContentToScratchSpace(inputStream);
- } catch (final Exception ex) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex);
- return null;
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (final IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e);
- }
- }
- }
- }
-
- /**
- * Persist a piece of content from the given input stream, byte by byte to the specified
- * directory.
- * @return the output Uri if the operation succeeded, or null if failed.
- */
- @DoesNotRunOnMainThread
- public static Uri persistContent(
- final InputStream inputStream, final File outputDir, final String contentType) {
- if (!outputDir.exists() && !outputDir.mkdirs()) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error creating " + outputDir.getAbsolutePath());
- return null;
- }
-
- final Context context = Factory.get().getApplicationContext();
- try {
- final Uri targetUri = Uri.fromFile(FileUtil.getNewFile(outputDir, contentType));
- return copyContent(context, inputStream, targetUri);
- } catch (final IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error creating file in " + outputDir.getAbsolutePath());
- return null;
- }
- }
-
- /**
- * Persist a piece of content from the given sourceUri, byte by byte to the
- * specified output directory.
- * @return the output Uri if the operation succeeded, or null if failed.
- */
- @DoesNotRunOnMainThread
- public static Uri persistContent(
- final Uri sourceUri, final File outputDir, final String contentType) {
- InputStream inputStream = null;
- final Context context = Factory.get().getApplicationContext();
- try {
- if (UriUtil.isLocalResourceUri(sourceUri)) {
- inputStream = context.getContentResolver().openInputStream(sourceUri);
- } else {
- // The content is remote. Download it.
- final URL url = new URL(sourceUri.toString());
- final URLConnection ucon = url.openConnection();
- inputStream = new BufferedInputStream(ucon.getInputStream());
- }
- return persistContent(inputStream, outputDir, contentType);
- } catch (final Exception ex) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex);
- return null;
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (final IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e);
- }
- }
- }
- }
-
- /** @return uri of target file, or null on error */
- @DoesNotRunOnMainThread
- private static Uri copyContent(
- final Context context, final InputStream inputStream, final Uri targetUri) {
- Assert.isNotMainThread();
- OutputStream outputStream = null;
- try {
- outputStream = context.getContentResolver().openOutputStream(targetUri);
- ByteStreams.copy(inputStream, outputStream);
- } catch (final Exception ex) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error while copying content ", ex);
- return null;
- } finally {
- if (outputStream != null) {
- try {
- outputStream.flush();
- } catch (final IOException e) {
- LogUtil.e(LogUtil.BUGLE_TAG, "error trying to flush the outputStream", e);
- return null;
- } finally {
- try {
- outputStream.close();
- } catch (final IOException e) {
- // Do nothing.
- }
- }
- }
- }
- return targetUri;
- }
-
- public static boolean isSmsMmsUri(final Uri uri) {
- return uri != null && SMS_MMS_SCHEMES.contains(uri.getScheme());
- }
-
- /**
- * Extract recipient destinations from Uri of form
- * SCHEME:destionation[,destination]?otherstuff
- * where SCHEME is one of the supported sms/mms schemes.
- *
- * @param uri sms/mms uri
- * @return recipient destinations or null
- */
- public static String[] parseRecipientsFromSmsMmsUri(final Uri uri) {
- if (!isSmsMmsUri(uri)) {
- return null;
- }
- final String[] parts = uri.getSchemeSpecificPart().split("\\?");
- if (TextUtils.isEmpty(parts[0])) {
- return null;
- }
- // replaceUnicodeDigits will replace digits typed in other languages (i.e. Egyptian) with
- // the usual ascii equivalents.
- return TextUtil.replaceUnicodeDigits(parts[0]).replace(';', ',').split(",");
- }
-
- /**
- * Return the length of the file to which contentUri refers
- *
- * @param contentUri URI for the file of which we want the length
- * @return Length of the file or AssetFileDescriptor.UNKNOWN_LENGTH
- */
- public static long getUriContentLength(final Uri contentUri) {
- final Context context = Factory.get().getApplicationContext();
- AssetFileDescriptor afd = null;
- try {
- afd = context.getContentResolver().openAssetFileDescriptor(contentUri, "r");
- return afd.getLength();
- } catch (final FileNotFoundException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Failed to query length of " + contentUri);
- } finally {
- if (afd != null) {
- try {
- afd.close();
- } catch (final IOException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Failed to close afd for " + contentUri);
- }
- }
- }
- return AssetFileDescriptor.UNKNOWN_LENGTH;
- }
-
- /**
- * Download data from the given url to the given local file.
- * @return true if the download was successful.
- *
- * TODO: Add retry/exponential backoff logic.
- */
- @DoesNotRunOnMainThread
- public static boolean downloadDataFromUrl(final String urlString, final File localFile) {
- Assert.isNotMainThread();
- LogUtil.i(LogUtil.BUGLE_TAG, "Downloading from " + urlString + " to " + localFile);
- try {
- Files.createParentDirs(localFile);
- final URL inUrl = new URL(urlString);
- ByteStreams.copy(Resources.newInputStreamSupplier(inUrl),
- Files.newOutputStreamSupplier(localFile));
- return true;
- } catch (final IOException ex) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error downloading from " + urlString, ex);
- return false;
- }
- }
-
- /** @return string representation of URI or null if URI was null */
- public static String stringFromUri(final Uri uri) {
- return uri == null ? null : uri.toString();
- }
-
- /** @return URI created from string or null if string was null or empty */
- public static Uri uriFromString(final String uriString) {
- return TextUtils.isEmpty(uriString) ? null : Uri.parse(uriString);
- }
-}
diff --git a/src/com/android/messaging/util/VersionUtil.java b/src/com/android/messaging/util/VersionUtil.java
deleted file mode 100644
index b87aa55..0000000
--- a/src/com/android/messaging/util/VersionUtil.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-
-import java.util.Locale;
-
-public final class VersionUtil {
- private static final Object sLock = new Object();
- private static VersionUtil sInstance;
- private final String mSimpleVersionName;
- private final int mVersionCode;
-
- public static VersionUtil getInstance(final Context context) {
- synchronized (sLock) {
- if (sInstance == null) {
- sInstance = new VersionUtil(context);
- }
- }
- return sInstance;
- }
-
- private VersionUtil(final Context context) {
- int versionCode;
- try {
- PackageInfo pi = context.getPackageManager().getPackageInfo(
- context.getPackageName(), 0);
- versionCode = pi.versionCode;
- } catch (final NameNotFoundException exception) {
- Assert.fail("couldn't get package info " + exception);
- versionCode = -1;
- }
- mVersionCode = versionCode;
- final int majorBuildNumber = versionCode / 1000;
- // Use US locale to format version number so that other language characters don't
- // show up in version string.
- mSimpleVersionName = String.format(Locale.US, "%d.%d.%03d",
- majorBuildNumber / 10000,
- (majorBuildNumber / 1000) % 10,
- majorBuildNumber % 1000);
- }
-
- public int getVersionCode() {
- return mVersionCode;
- }
-
- public String getSimpleName() {
- return mSimpleVersionName;
- }
-}
diff --git a/src/com/android/messaging/util/WakeLockHelper.java b/src/com/android/messaging/util/WakeLockHelper.java
deleted file mode 100644
index c9a9152..0000000
--- a/src/com/android/messaging/util/WakeLockHelper.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.util;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Debug;
-import android.os.PowerManager;
-import android.os.Process;
-
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Helper class used to manage wakelock state
- */
-public class WakeLockHelper {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- private static final boolean VERBOSE = false;
-
- @VisibleForTesting
- public static final String EXTRA_CALLING_PID = "pid";
-
- private final Object mLock = new Object();
- private final String mWakeLockId;
- private final int mMyPid;
-
- private PowerManager.WakeLock mWakeLock;
-
- public WakeLockHelper(final String wakeLockId) {
- mWakeLockId = wakeLockId;
- mMyPid = Process.myPid();
- }
-
- /**
- * Acquire the wakelock
- */
- public void acquire(final Context context, final Intent intent, final int opcode) {
- synchronized (mLock) {
- if (mWakeLock == null) {
- if (VERBOSE) {
- LogUtil.v(TAG, "initializing wakelock");
- }
- final PowerManager pm = (PowerManager)
- context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mWakeLockId);
- }
- }
- if (VERBOSE) {
- LogUtil.v(TAG, "acquiring " + mWakeLockId + " for opcode " + opcode);
- }
- mWakeLock.acquire();
- intent.putExtra(EXTRA_CALLING_PID, mMyPid);
- }
-
- /**
- * Check if wakelock held by this process
- */
- public boolean isHeld(final Intent intent) {
- final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1));
- return (respectWakeLock && mWakeLock.isHeld());
- }
-
- /**
- * Ensure that wakelock is held by this process
- */
- public boolean ensure(final Intent intent, final int opcode) {
- final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1));
- if (VERBOSE) {
- LogUtil.v(TAG, "WakeLockHelper.ensure Intent " + intent + " "
- + intent.getAction() + " opcode: " + opcode
- + " respectWakeLock " + respectWakeLock);
- }
-
- if (respectWakeLock) {
- final boolean isHeld = (respectWakeLock && isHeld(intent));
- if (!isHeld) {
- LogUtil.e(TAG, "WakeLockHelper.ensure called " + intent + " " + intent.getAction()
- + " opcode: " + opcode + " sWakeLock: " + mWakeLock + " isHeld: "
- + ((mWakeLock == null) ? "(null)" : mWakeLock.isHeld()));
- if (!Debug.isDebuggerConnected()) {
- Assert.fail("WakeLock dropped prior to service starting");
- }
- }
- return true;
- }
- return false;
- }
-
- /**
- * Release wakelock (if it is held by this process)
- */
- public void release(final Intent intent, final int opcode) {
- final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1));
- if (respectWakeLock) {
- try {
- mWakeLock.release();
- } catch (final RuntimeException ex) {
- LogUtil.e(TAG, "KeepAliveService.onHandleIntent exit crash " + intent + " "
- + intent.getAction() + " opcode: " + opcode + " sWakeLock: " + mWakeLock
- + " isHeld: " + ((mWakeLock == null) ? "(null)" : mWakeLock.isHeld()));
- if (!Debug.isDebuggerConnected()) {
- Assert.fail("WakeLock no longer held at end of handler");
- }
- }
- }
- }
-}
diff --git a/src/com/android/messaging/util/YouTubeUtil.java b/src/com/android/messaging/util/YouTubeUtil.java
deleted file mode 100644
index 203a666..0000000
--- a/src/com/android/messaging/util/YouTubeUtil.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.util;
-
-import android.net.Uri;
-import android.text.TextUtils;
-
-public class YouTubeUtil {
- private static final String YOUTUBE_HOST_1 = "www.youtube.com";
- private static final String YOUTUBE_HOST_2 = "youtube.com";
- private static final String YOUTUBE_HOST_3 = "m.youtube.com";
- private static final String YOUTUBE_HOST_4 = "youtube.googleapis.com";
- private static final String YOUTUBE_HOST_5 = "youtu.be";
-
- private static final String YOUTUBE_PATH_1 = "/watch";
- private static final String YOUTUBE_PATH_2 = "/embed/";
- private static final String YOUTUBE_PATH_3 = "/v/";
- private static final String YOUTUBE_PATH_4 = "/apiplayer";
-
- public static final String YOUTUBE_STATIC_THUMBNAIL_PREFIX = "https://img.youtube.com/vi/";
- public static final String YOUTUBE_STATIC_THUMBNAIL_END = "/hqdefault.jpg";
-
- public static String getYoutubePreviewImageLink(String urlString) {
- // Types of youtube urls:
- // 1.) http://www.youtube.com/watch?v=VIDEOID
- // 2.) http://www.youtube.com/embed/VIDEOID
- // 3.) http://www.youtube.com/v/VIDEOID
- // 3a.) https://youtube.googleapis.com/v/VIDEOID
- // 4.) http://www.youtube.com/apiplayer?video_id=VIDEO_ID
- // 5.) http://youtu.be/VIDEOID
- if (!urlString.startsWith("http")) {
- // Apparently the url is not an RFC 2396 compliant uri without the port
- urlString = "http://" + urlString;
- }
- final Uri uri = Uri.parse(urlString);
- final String host = uri.getHost();
- if (YOUTUBE_HOST_1.equalsIgnoreCase(host)
- || YOUTUBE_HOST_2.equalsIgnoreCase(host)
- || YOUTUBE_HOST_3.equalsIgnoreCase(host)
- || YOUTUBE_HOST_4.equalsIgnoreCase(host)
- || YOUTUBE_HOST_5.equalsIgnoreCase(host)) {
- final String videoId = getYouTubeVideoId(uri);
- if (!TextUtils.isEmpty(videoId)) {
- return YOUTUBE_STATIC_THUMBNAIL_PREFIX + videoId + YOUTUBE_STATIC_THUMBNAIL_END;
- }
- return null;
- }
- return null;
- }
-
- private static String getYouTubeVideoId(Uri uri) {
- final String urlPath = uri.getPath();
-
- if (TextUtils.isEmpty(urlPath)) {
- // There is no path so no need to continue.
- return null;
- }
- // Case 1
- if (urlPath.startsWith(YOUTUBE_PATH_1)) {
- return uri.getQueryParameter("v");
- }
- // Case 2
- if (urlPath.startsWith(YOUTUBE_PATH_2)) {
- return getVideoIdFromPath(YOUTUBE_PATH_2, urlPath);
- }
- // Case 3
- if (urlPath.startsWith(YOUTUBE_PATH_3)) {
- return getVideoIdFromPath(YOUTUBE_PATH_3, urlPath);
- }
- // Case 4
- if (urlPath.startsWith(YOUTUBE_PATH_4)) {
- return uri.getQueryParameter("video_id");
- }
- // Case 5
- if (YOUTUBE_HOST_5.equalsIgnoreCase(uri.getHost())) {
- return getVideoIdFromPath("/", urlPath);
- }
- return null;
- }
-
- private static String getVideoIdFromPath(String prefixSubstring, String urlPath) {
- return urlPath.substring(prefixSubstring.length());
- }
-
-}
diff --git a/src/com/android/messaging/util/exif/ByteBufferInputStream.java b/src/com/android/messaging/util/exif/ByteBufferInputStream.java
deleted file mode 100644
index 9db92ef..0000000
--- a/src/com/android/messaging/util/exif/ByteBufferInputStream.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-class ByteBufferInputStream extends InputStream {
-
- private final ByteBuffer mBuf;
-
- public ByteBufferInputStream(ByteBuffer buf) {
- mBuf = buf;
- }
-
- @Override
- public int read() {
- if (!mBuf.hasRemaining()) {
- return -1;
- }
- return mBuf.get() & 0xFF;
- }
-
- @Override
- public int read(byte[] bytes, int off, int len) {
- if (!mBuf.hasRemaining()) {
- return -1;
- }
-
- len = Math.min(len, mBuf.remaining());
- mBuf.get(bytes, off, len);
- return len;
- }
-}
diff --git a/src/com/android/messaging/util/exif/CountedDataInputStream.java b/src/com/android/messaging/util/exif/CountedDataInputStream.java
deleted file mode 100644
index ce766d9..0000000
--- a/src/com/android/messaging/util/exif/CountedDataInputStream.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import java.io.EOFException;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-
-class CountedDataInputStream extends FilterInputStream {
-
- private int mCount = 0;
-
- // allocate a byte buffer for a long value;
- private final byte mByteArray[] = new byte[8];
- private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);
-
- protected CountedDataInputStream(InputStream in) {
- super(in);
- }
-
- public int getReadByteCount() {
- return mCount;
- }
-
- @Override
- public int read(byte[] b) throws IOException {
- int r = in.read(b);
- mCount += (r >= 0) ? r : 0;
- return r;
- }
-
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- int r = in.read(b, off, len);
- mCount += (r >= 0) ? r : 0;
- return r;
- }
-
- @Override
- public int read() throws IOException {
- int r = in.read();
- mCount += (r >= 0) ? 1 : 0;
- return r;
- }
-
- @Override
- public long skip(long length) throws IOException {
- long skip = in.skip(length);
- mCount += skip;
- return skip;
- }
-
- public void skipOrThrow(long length) throws IOException {
- if (skip(length) != length) {
- throw new EOFException();
- }
- }
-
- public void skipTo(long target) throws IOException {
- long cur = mCount;
- long diff = target - cur;
- assert(diff >= 0);
- skipOrThrow(diff);
- }
-
- public void readOrThrow(byte[] b, int off, int len) throws IOException {
- int r = read(b, off, len);
- if (r != len) {
- throw new EOFException();
- }
- }
-
- public void readOrThrow(byte[] b) throws IOException {
- readOrThrow(b, 0, b.length);
- }
-
- public void setByteOrder(ByteOrder order) {
- mByteBuffer.order(order);
- }
-
- public ByteOrder getByteOrder() {
- return mByteBuffer.order();
- }
-
- public short readShort() throws IOException {
- readOrThrow(mByteArray, 0 , 2);
- mByteBuffer.rewind();
- return mByteBuffer.getShort();
- }
-
- public int readUnsignedShort() throws IOException {
- return readShort() & 0xffff;
- }
-
- public int readInt() throws IOException {
- readOrThrow(mByteArray, 0 , 4);
- mByteBuffer.rewind();
- return mByteBuffer.getInt();
- }
-
- public long readUnsignedInt() throws IOException {
- return readInt() & 0xffffffffL;
- }
-
- public long readLong() throws IOException {
- readOrThrow(mByteArray, 0 , 8);
- mByteBuffer.rewind();
- return mByteBuffer.getLong();
- }
-
- public String readString(int n) throws IOException {
- byte buf[] = new byte[n];
- readOrThrow(buf);
- return new String(buf, "UTF8");
- }
-
- public String readString(int n, Charset charset) throws IOException {
- byte buf[] = new byte[n];
- readOrThrow(buf);
- return new String(buf, charset);
- }
-}
diff --git a/src/com/android/messaging/util/exif/ExifData.java b/src/com/android/messaging/util/exif/ExifData.java
deleted file mode 100644
index 77ba4e9..0000000
--- a/src/com/android/messaging/util/exif/ExifData.java
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import android.util.Log;
-import com.android.messaging.util.LogUtil;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * This class stores the EXIF header in IFDs according to the JPEG
- * specification. It is the result produced by {@link ExifReader}.
- *
- * @see ExifReader
- * @see IfdData
- */
-class ExifData {
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final byte[] USER_COMMENT_ASCII = {
- 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
- };
- private static final byte[] USER_COMMENT_JIS = {
- 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
- };
- private static final byte[] USER_COMMENT_UNICODE = {
- 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
- };
-
- private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
- private byte[] mThumbnail;
- private final ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
- private final ByteOrder mByteOrder;
-
- ExifData(ByteOrder order) {
- mByteOrder = order;
- }
-
- /**
- * Gets the compressed thumbnail. Returns null if there is no compressed
- * thumbnail.
- *
- * @see #hasCompressedThumbnail()
- */
- protected byte[] getCompressedThumbnail() {
- return mThumbnail;
- }
-
- /**
- * Sets the compressed thumbnail.
- */
- protected void setCompressedThumbnail(byte[] thumbnail) {
- mThumbnail = thumbnail;
- }
-
- /**
- * Returns true it this header contains a compressed thumbnail.
- */
- protected boolean hasCompressedThumbnail() {
- return mThumbnail != null;
- }
-
- /**
- * Adds an uncompressed strip.
- */
- protected void setStripBytes(int index, byte[] strip) {
- if (index < mStripBytes.size()) {
- mStripBytes.set(index, strip);
- } else {
- for (int i = mStripBytes.size(); i < index; i++) {
- mStripBytes.add(null);
- }
- mStripBytes.add(strip);
- }
- }
-
- /**
- * Gets the strip count.
- */
- protected int getStripCount() {
- return mStripBytes.size();
- }
-
- /**
- * Gets the strip at the specified index.
- *
- * @exceptions #IndexOutOfBoundException
- */
- protected byte[] getStrip(int index) {
- return mStripBytes.get(index);
- }
-
- /**
- * Returns true if this header contains uncompressed strip.
- */
- protected boolean hasUncompressedStrip() {
- return mStripBytes.size() != 0;
- }
-
- /**
- * Gets the byte order.
- */
- protected ByteOrder getByteOrder() {
- return mByteOrder;
- }
-
- /**
- * Returns the {@link IfdData} object corresponding to a given IFD if it
- * exists or null.
- */
- protected IfdData getIfdData(int ifdId) {
- if (ExifTag.isValidIfd(ifdId)) {
- return mIfdDatas[ifdId];
- }
- return null;
- }
-
- /**
- * Adds IFD data. If IFD data of the same type already exists, it will be
- * replaced by the new data.
- */
- protected void addIfdData(IfdData data) {
- mIfdDatas[data.getId()] = data;
- }
-
- /**
- * Returns the {@link IfdData} object corresponding to a given IFD or
- * generates one if none exist.
- */
- protected IfdData getOrCreateIfdData(int ifdId) {
- IfdData ifdData = mIfdDatas[ifdId];
- if (ifdData == null) {
- ifdData = new IfdData(ifdId);
- mIfdDatas[ifdId] = ifdData;
- }
- return ifdData;
- }
-
- /**
- * Returns the tag with a given TID in the given IFD if the tag exists.
- * Otherwise returns null.
- */
- protected ExifTag getTag(short tag, int ifd) {
- IfdData ifdData = mIfdDatas[ifd];
- return (ifdData == null) ? null : ifdData.getTag(tag);
- }
-
- /**
- * Adds the given ExifTag to its default IFD and returns an existing ExifTag
- * with the same TID or null if none exist.
- */
- protected ExifTag addTag(ExifTag tag) {
- if (tag != null) {
- int ifd = tag.getIfd();
- return addTag(tag, ifd);
- }
- return null;
- }
-
- /**
- * Adds the given ExifTag to the given IFD and returns an existing ExifTag
- * with the same TID or null if none exist.
- */
- protected ExifTag addTag(ExifTag tag, int ifdId) {
- if (tag != null && ExifTag.isValidIfd(ifdId)) {
- IfdData ifdData = getOrCreateIfdData(ifdId);
- return ifdData.setTag(tag);
- }
- return null;
- }
-
- protected void clearThumbnailAndStrips() {
- mThumbnail = null;
- mStripBytes.clear();
- }
-
- /**
- * Removes the thumbnail and its related tags. IFD1 will be removed.
- */
- protected void removeThumbnailData() {
- clearThumbnailAndStrips();
- mIfdDatas[IfdId.TYPE_IFD_1] = null;
- }
-
- /**
- * Removes the tag with a given TID and IFD.
- */
- protected void removeTag(short tagId, int ifdId) {
- IfdData ifdData = mIfdDatas[ifdId];
- if (ifdData == null) {
- return;
- }
- ifdData.removeTag(tagId);
- }
-
- /**
- * Decodes the user comment tag into string as specified in the EXIF
- * standard. Returns null if decoding failed.
- */
- protected String getUserComment() {
- IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
- if (ifdData == null) {
- return null;
- }
- ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
- if (tag == null) {
- return null;
- }
- if (tag.getComponentCount() < 8) {
- return null;
- }
-
- byte[] buf = new byte[tag.getComponentCount()];
- tag.getBytes(buf);
-
- byte[] code = new byte[8];
- System.arraycopy(buf, 0, code, 0, 8);
-
- try {
- if (Arrays.equals(code, USER_COMMENT_ASCII)) {
- return new String(buf, 8, buf.length - 8, "US-ASCII");
- } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
- return new String(buf, 8, buf.length - 8, "EUC-JP");
- } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
- return new String(buf, 8, buf.length - 8, "UTF-16");
- } else {
- return null;
- }
- } catch (UnsupportedEncodingException e) {
- Log.w(TAG, "Failed to decode the user comment");
- return null;
- }
- }
-
- /**
- * Returns a list of all {@link ExifTag}s in the ExifData or null if there
- * are none.
- */
- protected List<ExifTag> getAllTags() {
- ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
- for (IfdData d : mIfdDatas) {
- if (d != null) {
- ExifTag[] tags = d.getAllTags();
- if (tags != null) {
- for (ExifTag t : tags) {
- ret.add(t);
- }
- }
- }
- }
- if (ret.size() == 0) {
- return null;
- }
- return ret;
- }
-
- /**
- * Returns a list of all {@link ExifTag}s in a given IFD or null if there
- * are none.
- */
- protected List<ExifTag> getAllTagsForIfd(int ifd) {
- IfdData d = mIfdDatas[ifd];
- if (d == null) {
- return null;
- }
- ExifTag[] tags = d.getAllTags();
- if (tags == null) {
- return null;
- }
- ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
- for (ExifTag t : tags) {
- ret.add(t);
- }
- if (ret.size() == 0) {
- return null;
- }
- return ret;
- }
-
- /**
- * Returns a list of all {@link ExifTag}s with a given TID or null if there
- * are none.
- */
- protected List<ExifTag> getAllTagsForTagId(short tag) {
- ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
- for (IfdData d : mIfdDatas) {
- if (d != null) {
- ExifTag t = d.getTag(tag);
- if (t != null) {
- ret.add(t);
- }
- }
- }
- if (ret.size() == 0) {
- return null;
- }
- return ret;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (obj instanceof ExifData) {
- ExifData data = (ExifData) obj;
- if (data.mByteOrder != mByteOrder ||
- data.mStripBytes.size() != mStripBytes.size() ||
- !Arrays.equals(data.mThumbnail, mThumbnail)) {
- return false;
- }
- for (int i = 0; i < mStripBytes.size(); i++) {
- if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
- return false;
- }
- }
- for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
- IfdData ifd1 = data.getIfdData(i);
- IfdData ifd2 = getIfdData(i);
- if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
- return false;
- }
- }
- return true;
- }
- return false;
- }
-
-}
diff --git a/src/com/android/messaging/util/exif/ExifInterface.java b/src/com/android/messaging/util/exif/ExifInterface.java
deleted file mode 100644
index b556748..0000000
--- a/src/com/android/messaging/util/exif/ExifInterface.java
+++ /dev/null
@@ -1,2448 +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.messaging.util.exif;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.SparseIntArray;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.FileChannel.MapMode;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.TimeZone;
-
-/**
- * This class provides methods and constants for reading and writing jpeg file
- * metadata. It contains a collection of ExifTags, and a collection of
- * definitions for creating valid ExifTags. The collection of ExifTags can be
- * updated by: reading new ones from a file, deleting or adding existing ones,
- * or building new ExifTags from a tag definition. These ExifTags can be written
- * to a valid jpeg image as exif metadata.
- * <p>
- * Each ExifTag has a tag ID (TID) and is stored in a specific image file
- * directory (IFD) as specified by the exif standard. A tag definition can be
- * looked up with a constant that is a combination of TID and IFD. This
- * definition has information about the type, number of components, and valid
- * IFDs for a tag.
- *
- * @see ExifTag
- */
-public class ExifInterface {
- public static final int TAG_NULL = -1;
- public static final int IFD_NULL = -1;
- public static final int DEFINITION_NULL = 0;
-
- /**
- * Tag constants for Jeita EXIF 2.2
- */
-
- // IFD 0
- public static final int TAG_IMAGE_WIDTH =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
- public static final int TAG_IMAGE_LENGTH =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
- public static final int TAG_BITS_PER_SAMPLE =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
- public static final int TAG_COMPRESSION =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
- public static final int TAG_PHOTOMETRIC_INTERPRETATION =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
- public static final int TAG_IMAGE_DESCRIPTION =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
- public static final int TAG_MAKE =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
- public static final int TAG_MODEL =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
- public static final int TAG_STRIP_OFFSETS =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
- public static final int TAG_ORIENTATION =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
- public static final int TAG_SAMPLES_PER_PIXEL =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
- public static final int TAG_ROWS_PER_STRIP =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
- public static final int TAG_STRIP_BYTE_COUNTS =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
- public static final int TAG_X_RESOLUTION =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
- public static final int TAG_Y_RESOLUTION =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
- public static final int TAG_PLANAR_CONFIGURATION =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
- public static final int TAG_RESOLUTION_UNIT =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
- public static final int TAG_TRANSFER_FUNCTION =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
- public static final int TAG_SOFTWARE =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
- public static final int TAG_DATE_TIME =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
- public static final int TAG_ARTIST =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
- public static final int TAG_WHITE_POINT =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
- public static final int TAG_PRIMARY_CHROMATICITIES =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
- public static final int TAG_Y_CB_CR_COEFFICIENTS =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
- public static final int TAG_Y_CB_CR_SUB_SAMPLING =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
- public static final int TAG_Y_CB_CR_POSITIONING =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
- public static final int TAG_REFERENCE_BLACK_WHITE =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
- public static final int TAG_COPYRIGHT =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
- public static final int TAG_EXIF_IFD =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
- public static final int TAG_GPS_IFD =
- defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
- // IFD 1
- public static final int TAG_JPEG_INTERCHANGE_FORMAT =
- defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
- public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
- defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
- // IFD Exif Tags
- public static final int TAG_EXPOSURE_TIME =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
- public static final int TAG_F_NUMBER =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
- public static final int TAG_EXPOSURE_PROGRAM =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
- public static final int TAG_SPECTRAL_SENSITIVITY =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
- public static final int TAG_ISO_SPEED_RATINGS =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
- public static final int TAG_OECF =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
- public static final int TAG_EXIF_VERSION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
- public static final int TAG_DATE_TIME_ORIGINAL =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
- public static final int TAG_DATE_TIME_DIGITIZED =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
- public static final int TAG_COMPONENTS_CONFIGURATION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
- public static final int TAG_COMPRESSED_BITS_PER_PIXEL =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
- public static final int TAG_SHUTTER_SPEED_VALUE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
- public static final int TAG_APERTURE_VALUE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
- public static final int TAG_BRIGHTNESS_VALUE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
- public static final int TAG_EXPOSURE_BIAS_VALUE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
- public static final int TAG_MAX_APERTURE_VALUE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
- public static final int TAG_SUBJECT_DISTANCE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
- public static final int TAG_METERING_MODE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
- public static final int TAG_LIGHT_SOURCE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
- public static final int TAG_FLASH =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
- public static final int TAG_FOCAL_LENGTH =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
- public static final int TAG_SUBJECT_AREA =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
- public static final int TAG_MAKER_NOTE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
- public static final int TAG_USER_COMMENT =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
- public static final int TAG_SUB_SEC_TIME =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
- public static final int TAG_SUB_SEC_TIME_ORIGINAL =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
- public static final int TAG_SUB_SEC_TIME_DIGITIZED =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
- public static final int TAG_FLASHPIX_VERSION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
- public static final int TAG_COLOR_SPACE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
- public static final int TAG_PIXEL_X_DIMENSION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
- public static final int TAG_PIXEL_Y_DIMENSION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
- public static final int TAG_RELATED_SOUND_FILE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
- public static final int TAG_INTEROPERABILITY_IFD =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
- public static final int TAG_FLASH_ENERGY =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
- public static final int TAG_SPATIAL_FREQUENCY_RESPONSE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
- public static final int TAG_FOCAL_PLANE_X_RESOLUTION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
- public static final int TAG_FOCAL_PLANE_Y_RESOLUTION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
- public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
- public static final int TAG_SUBJECT_LOCATION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
- public static final int TAG_EXPOSURE_INDEX =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
- public static final int TAG_SENSING_METHOD =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
- public static final int TAG_FILE_SOURCE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
- public static final int TAG_SCENE_TYPE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
- public static final int TAG_CFA_PATTERN =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
- public static final int TAG_CUSTOM_RENDERED =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
- public static final int TAG_EXPOSURE_MODE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
- public static final int TAG_WHITE_BALANCE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
- public static final int TAG_DIGITAL_ZOOM_RATIO =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
- public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
- public static final int TAG_SCENE_CAPTURE_TYPE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
- public static final int TAG_GAIN_CONTROL =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
- public static final int TAG_CONTRAST =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
- public static final int TAG_SATURATION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
- public static final int TAG_SHARPNESS =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
- public static final int TAG_DEVICE_SETTING_DESCRIPTION =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
- public static final int TAG_SUBJECT_DISTANCE_RANGE =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
- public static final int TAG_IMAGE_UNIQUE_ID =
- defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
- // IFD GPS tags
- public static final int TAG_GPS_VERSION_ID =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
- public static final int TAG_GPS_LATITUDE_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
- public static final int TAG_GPS_LATITUDE =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
- public static final int TAG_GPS_LONGITUDE_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
- public static final int TAG_GPS_LONGITUDE =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
- public static final int TAG_GPS_ALTITUDE_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
- public static final int TAG_GPS_ALTITUDE =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
- public static final int TAG_GPS_TIME_STAMP =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
- public static final int TAG_GPS_SATTELLITES =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
- public static final int TAG_GPS_STATUS =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
- public static final int TAG_GPS_MEASURE_MODE =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
- public static final int TAG_GPS_DOP =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
- public static final int TAG_GPS_SPEED_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
- public static final int TAG_GPS_SPEED =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
- public static final int TAG_GPS_TRACK_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
- public static final int TAG_GPS_TRACK =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
- public static final int TAG_GPS_IMG_DIRECTION_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
- public static final int TAG_GPS_IMG_DIRECTION =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
- public static final int TAG_GPS_MAP_DATUM =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
- public static final int TAG_GPS_DEST_LATITUDE_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
- public static final int TAG_GPS_DEST_LATITUDE =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
- public static final int TAG_GPS_DEST_LONGITUDE_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
- public static final int TAG_GPS_DEST_LONGITUDE =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
- public static final int TAG_GPS_DEST_BEARING_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
- public static final int TAG_GPS_DEST_BEARING =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
- public static final int TAG_GPS_DEST_DISTANCE_REF =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
- public static final int TAG_GPS_DEST_DISTANCE =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
- public static final int TAG_GPS_PROCESSING_METHOD =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
- public static final int TAG_GPS_AREA_INFORMATION =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
- public static final int TAG_GPS_DATE_STAMP =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
- public static final int TAG_GPS_DIFFERENTIAL =
- defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
- // IFD Interoperability tags
- public static final int TAG_INTEROPERABILITY_INDEX =
- defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
-
- /**
- * Tags that contain offset markers. These are included in the banned
- * defines.
- */
- private static HashSet<Short> sOffsetTags = new HashSet<Short>();
- static {
- sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
- sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
- sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
- sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
- sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
- }
-
- /**
- * Tags with definitions that cannot be overridden (banned defines).
- */
- protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);
- static {
- sBannedDefines.add(getTrueTagKey(TAG_NULL));
- sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
- sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
- }
-
- /**
- * Returns the constant representing a tag with a given TID and default IFD.
- */
- public static int defineTag(int ifdId, short tagId) {
- return (tagId & 0x0000ffff) | (ifdId << 16);
- }
-
- /**
- * Returns the TID for a tag constant.
- */
- public static short getTrueTagKey(int tag) {
- // Truncate
- return (short) tag;
- }
-
- /**
- * Returns the default IFD for a tag constant.
- */
- public static int getTrueIfd(int tag) {
- return tag >>> 16;
- }
-
- /**
- * Constants for {@link TAG_ORIENTATION}. They can be interpreted as
- * follows:
- * <ul>
- * <li>TOP_LEFT is the normal orientation.</li>
- * <li>TOP_RIGHT is a left-right mirror.</li>
- * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
- * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
- * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
- * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
- * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
- * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
- * </ul>
- */
- public static interface Orientation {
- public static final short TOP_LEFT = 1;
- public static final short TOP_RIGHT = 2;
- public static final short BOTTOM_LEFT = 3;
- public static final short BOTTOM_RIGHT = 4;
- public static final short LEFT_TOP = 5;
- public static final short RIGHT_TOP = 6;
- public static final short LEFT_BOTTOM = 7;
- public static final short RIGHT_BOTTOM = 8;
- }
-
- /**
- * Constants for {@link TAG_Y_CB_CR_POSITIONING}
- */
- public static interface YCbCrPositioning {
- public static final short CENTERED = 1;
- public static final short CO_SITED = 2;
- }
-
- /**
- * Constants for {@link TAG_COMPRESSION}
- */
- public static interface Compression {
- public static final short UNCOMPRESSION = 1;
- public static final short JPEG = 6;
- }
-
- /**
- * Constants for {@link TAG_RESOLUTION_UNIT}
- */
- public static interface ResolutionUnit {
- public static final short INCHES = 2;
- public static final short CENTIMETERS = 3;
- }
-
- /**
- * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION}
- */
- public static interface PhotometricInterpretation {
- public static final short RGB = 2;
- public static final short YCBCR = 6;
- }
-
- /**
- * Constants for {@link TAG_PLANAR_CONFIGURATION}
- */
- public static interface PlanarConfiguration {
- public static final short CHUNKY = 1;
- public static final short PLANAR = 2;
- }
-
- /**
- * Constants for {@link TAG_EXPOSURE_PROGRAM}
- */
- public static interface ExposureProgram {
- public static final short NOT_DEFINED = 0;
- public static final short MANUAL = 1;
- public static final short NORMAL_PROGRAM = 2;
- public static final short APERTURE_PRIORITY = 3;
- public static final short SHUTTER_PRIORITY = 4;
- public static final short CREATIVE_PROGRAM = 5;
- public static final short ACTION_PROGRAM = 6;
- public static final short PROTRAIT_MODE = 7;
- public static final short LANDSCAPE_MODE = 8;
- }
-
- /**
- * Constants for {@link TAG_METERING_MODE}
- */
- public static interface MeteringMode {
- public static final short UNKNOWN = 0;
- public static final short AVERAGE = 1;
- public static final short CENTER_WEIGHTED_AVERAGE = 2;
- public static final short SPOT = 3;
- public static final short MULTISPOT = 4;
- public static final short PATTERN = 5;
- public static final short PARTAIL = 6;
- public static final short OTHER = 255;
- }
-
- /**
- * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2
- * standard, we can treat this constant as bitwise flag.
- * <p>
- * e.g.
- * <p>
- * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED |
- * MODE_AUTO_MODE
- */
- public static interface Flash {
- // LSB
- public static final short DID_NOT_FIRED = 0;
- public static final short FIRED = 1;
- // 1st~2nd bits
- public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
- public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
- public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
- // 3rd~4th bits
- public static final short MODE_UNKNOWN = 0 << 3;
- public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
- public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
- public static final short MODE_AUTO_MODE = 3 << 3;
- // 5th bit
- public static final short FUNCTION_PRESENT = 0 << 5;
- public static final short FUNCTION_NO_FUNCTION = 1 << 5;
- // 6th bit
- public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
- public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
- }
-
- /**
- * Constants for {@link TAG_COLOR_SPACE}
- */
- public static interface ColorSpace {
- public static final short SRGB = 1;
- public static final short UNCALIBRATED = (short) 0xFFFF;
- }
-
- /**
- * Constants for {@link TAG_EXPOSURE_MODE}
- */
- public static interface ExposureMode {
- public static final short AUTO_EXPOSURE = 0;
- public static final short MANUAL_EXPOSURE = 1;
- public static final short AUTO_BRACKET = 2;
- }
-
- /**
- * Constants for {@link TAG_WHITE_BALANCE}
- */
- public static interface WhiteBalance {
- public static final short AUTO = 0;
- public static final short MANUAL = 1;
- }
-
- /**
- * Constants for {@link TAG_SCENE_CAPTURE_TYPE}
- */
- public static interface SceneCapture {
- public static final short STANDARD = 0;
- public static final short LANDSCAPE = 1;
- public static final short PROTRAIT = 2;
- public static final short NIGHT_SCENE = 3;
- }
-
- /**
- * Constants for {@link TAG_COMPONENTS_CONFIGURATION}
- */
- public static interface ComponentsConfiguration {
- public static final short NOT_EXIST = 0;
- public static final short Y = 1;
- public static final short CB = 2;
- public static final short CR = 3;
- public static final short R = 4;
- public static final short G = 5;
- public static final short B = 6;
- }
-
- /**
- * Constants for {@link TAG_LIGHT_SOURCE}
- */
- public static interface LightSource {
- public static final short UNKNOWN = 0;
- public static final short DAYLIGHT = 1;
- public static final short FLUORESCENT = 2;
- public static final short TUNGSTEN = 3;
- public static final short FLASH = 4;
- public static final short FINE_WEATHER = 9;
- public static final short CLOUDY_WEATHER = 10;
- public static final short SHADE = 11;
- public static final short DAYLIGHT_FLUORESCENT = 12;
- public static final short DAY_WHITE_FLUORESCENT = 13;
- public static final short COOL_WHITE_FLUORESCENT = 14;
- public static final short WHITE_FLUORESCENT = 15;
- public static final short STANDARD_LIGHT_A = 17;
- public static final short STANDARD_LIGHT_B = 18;
- public static final short STANDARD_LIGHT_C = 19;
- public static final short D55 = 20;
- public static final short D65 = 21;
- public static final short D75 = 22;
- public static final short D50 = 23;
- public static final short ISO_STUDIO_TUNGSTEN = 24;
- public static final short OTHER = 255;
- }
-
- /**
- * Constants for {@link TAG_SENSING_METHOD}
- */
- public static interface SensingMethod {
- public static final short NOT_DEFINED = 1;
- public static final short ONE_CHIP_COLOR = 2;
- public static final short TWO_CHIP_COLOR = 3;
- public static final short THREE_CHIP_COLOR = 4;
- public static final short COLOR_SEQUENTIAL_AREA = 5;
- public static final short TRILINEAR = 7;
- public static final short COLOR_SEQUENTIAL_LINEAR = 8;
- }
-
- /**
- * Constants for {@link TAG_FILE_SOURCE}
- */
- public static interface FileSource {
- public static final short DSC = 3;
- }
-
- /**
- * Constants for {@link TAG_SCENE_TYPE}
- */
- public static interface SceneType {
- public static final short DIRECT_PHOTOGRAPHED = 1;
- }
-
- /**
- * Constants for {@link TAG_GAIN_CONTROL}
- */
- public static interface GainControl {
- public static final short NONE = 0;
- public static final short LOW_UP = 1;
- public static final short HIGH_UP = 2;
- public static final short LOW_DOWN = 3;
- public static final short HIGH_DOWN = 4;
- }
-
- /**
- * Constants for {@link TAG_CONTRAST}
- */
- public static interface Contrast {
- public static final short NORMAL = 0;
- public static final short SOFT = 1;
- public static final short HARD = 2;
- }
-
- /**
- * Constants for {@link TAG_SATURATION}
- */
- public static interface Saturation {
- public static final short NORMAL = 0;
- public static final short LOW = 1;
- public static final short HIGH = 2;
- }
-
- /**
- * Constants for {@link TAG_SHARPNESS}
- */
- public static interface Sharpness {
- public static final short NORMAL = 0;
- public static final short SOFT = 1;
- public static final short HARD = 2;
- }
-
- /**
- * Constants for {@link TAG_SUBJECT_DISTANCE}
- */
- public static interface SubjectDistance {
- public static final short UNKNOWN = 0;
- public static final short MACRO = 1;
- public static final short CLOSE_VIEW = 2;
- public static final short DISTANT_VIEW = 3;
- }
-
- /**
- * Constants for {@link TAG_GPS_LATITUDE_REF},
- * {@link TAG_GPS_DEST_LATITUDE_REF}
- */
- public static interface GpsLatitudeRef {
- public static final String NORTH = "N";
- public static final String SOUTH = "S";
- }
-
- /**
- * Constants for {@link TAG_GPS_LONGITUDE_REF},
- * {@link TAG_GPS_DEST_LONGITUDE_REF}
- */
- public static interface GpsLongitudeRef {
- public static final String EAST = "E";
- public static final String WEST = "W";
- }
-
- /**
- * Constants for {@link TAG_GPS_ALTITUDE_REF}
- */
- public static interface GpsAltitudeRef {
- public static final short SEA_LEVEL = 0;
- public static final short SEA_LEVEL_NEGATIVE = 1;
- }
-
- /**
- * Constants for {@link TAG_GPS_STATUS}
- */
- public static interface GpsStatus {
- public static final String IN_PROGRESS = "A";
- public static final String INTEROPERABILITY = "V";
- }
-
- /**
- * Constants for {@link TAG_GPS_MEASURE_MODE}
- */
- public static interface GpsMeasureMode {
- public static final String MODE_2_DIMENSIONAL = "2";
- public static final String MODE_3_DIMENSIONAL = "3";
- }
-
- /**
- * Constants for {@link TAG_GPS_SPEED_REF},
- * {@link TAG_GPS_DEST_DISTANCE_REF}
- */
- public static interface GpsSpeedRef {
- public static final String KILOMETERS = "K";
- public static final String MILES = "M";
- public static final String KNOTS = "N";
- }
-
- /**
- * Constants for {@link TAG_GPS_TRACK_REF},
- * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF}
- */
- public static interface GpsTrackRef {
- public static final String TRUE_DIRECTION = "T";
- public static final String MAGNETIC_DIRECTION = "M";
- }
-
- /**
- * Constants for {@link TAG_GPS_DIFFERENTIAL}
- */
- public static interface GpsDifferential {
- public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
- public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
- }
-
- private static final String NULL_ARGUMENT_STRING = "Argument is null";
- private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
- public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
-
- public ExifInterface() {
- mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- }
-
- /**
- * Reads the exif tags from a byte array, clearing this ExifInterface
- * object's existing exif tags.
- *
- * @param jpeg a byte array containing a jpeg compressed image.
- * @throws java.io.IOException
- */
- public void readExif(byte[] jpeg) throws IOException {
- readExif(new ByteArrayInputStream(jpeg));
- }
-
- /**
- * Reads the exif tags from an InputStream, clearing this ExifInterface
- * object's existing exif tags.
- *
- * @param inStream an InputStream containing a jpeg compressed image.
- * @throws java.io.IOException
- */
- public void readExif(InputStream inStream) throws IOException {
- if (inStream == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- ExifData d = null;
- try {
- d = new ExifReader(this).read(inStream);
- } catch (ExifInvalidFormatException e) {
- throw new IOException("Invalid exif format : " + e);
- }
- mData = d;
- }
-
- /**
- * Reads the exif tags from a file, clearing this ExifInterface object's
- * existing exif tags.
- *
- * @param inFileName a string representing the filepath to jpeg file.
- * @throws java.io.FileNotFoundException
- * @throws java.io.IOException
- */
- public void readExif(String inFileName) throws FileNotFoundException, IOException {
- if (inFileName == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- InputStream is = null;
- try {
- is = new BufferedInputStream(new FileInputStream(inFileName));
- readExif(is);
- } catch (IOException e) {
- closeSilently(is);
- throw e;
- }
- is.close();
- }
-
- /**
- * Sets the exif tags, clearing this ExifInterface object's existing exif
- * tags.
- *
- * @param tags a collection of exif tags to set.
- */
- public void setExif(Collection<ExifTag> tags) {
- clearExif();
- setTags(tags);
- }
-
- /**
- * Clears this ExifInterface object's existing exif tags.
- */
- public void clearExif() {
- mData = new ExifData(DEFAULT_BYTE_ORDER);
- }
-
- /**
- * Writes the tags from this ExifInterface object into a jpeg image,
- * removing prior exif tags.
- *
- * @param jpeg a byte array containing a jpeg compressed image.
- * @param exifOutStream an OutputStream to which the jpeg image with added
- * exif tags will be written.
- * @throws java.io.IOException
- */
- public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException {
- if (jpeg == null || exifOutStream == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- OutputStream s = getExifWriterStream(exifOutStream);
- s.write(jpeg, 0, jpeg.length);
- s.flush();
- }
-
- /**
- * Writes the tags from this ExifInterface object into a jpeg compressed
- * bitmap, removing prior exif tags.
- *
- * @param bmap a bitmap to compress and write exif into.
- * @param exifOutStream the OutputStream to which the jpeg image with added
- * exif tags will be written.
- * @throws java.io.IOException
- */
- public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
- if (bmap == null || exifOutStream == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- OutputStream s = getExifWriterStream(exifOutStream);
- bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
- s.flush();
- }
-
- /**
- * Writes the tags from this ExifInterface object into a jpeg stream,
- * removing prior exif tags.
- *
- * @param jpegStream an InputStream containing a jpeg compressed image.
- * @param exifOutStream an OutputStream to which the jpeg image with added
- * exif tags will be written.
- * @throws java.io.IOException
- */
- public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException {
- if (jpegStream == null || exifOutStream == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- OutputStream s = getExifWriterStream(exifOutStream);
- doExifStreamIO(jpegStream, s);
- s.flush();
- }
-
- /**
- * Writes the tags from this ExifInterface object into a jpeg image,
- * removing prior exif tags.
- *
- * @param jpeg a byte array containing a jpeg compressed image.
- * @param exifOutFileName a String containing the filepath to which the jpeg
- * image with added exif tags will be written.
- * @throws java.io.FileNotFoundException
- * @throws java.io.IOException
- */
- public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException,
- IOException {
- if (jpeg == null || exifOutFileName == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- OutputStream s = null;
- try {
- s = getExifWriterStream(exifOutFileName);
- s.write(jpeg, 0, jpeg.length);
- s.flush();
- } catch (IOException e) {
- closeSilently(s);
- throw e;
- }
- s.close();
- }
-
- /**
- * Writes the tags from this ExifInterface object into a jpeg compressed
- * bitmap, removing prior exif tags.
- *
- * @param bmap a bitmap to compress and write exif into.
- * @param exifOutFileName a String containing the filepath to which the jpeg
- * image with added exif tags will be written.
- * @throws java.io.FileNotFoundException
- * @throws java.io.IOException
- */
- public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException,
- IOException {
- if (bmap == null || exifOutFileName == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- OutputStream s = null;
- try {
- s = getExifWriterStream(exifOutFileName);
- bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
- s.flush();
- } catch (IOException e) {
- closeSilently(s);
- throw e;
- }
- s.close();
- }
-
- /**
- * Writes the tags from this ExifInterface object into a jpeg stream,
- * removing prior exif tags.
- *
- * @param jpegStream an InputStream containing a jpeg compressed image.
- * @param exifOutFileName a String containing the filepath to which the jpeg
- * image with added exif tags will be written.
- * @throws java.io.FileNotFoundException
- * @throws java.io.IOException
- */
- public void writeExif(InputStream jpegStream, String exifOutFileName)
- throws FileNotFoundException, IOException {
- if (jpegStream == null || exifOutFileName == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- OutputStream s = null;
- try {
- s = getExifWriterStream(exifOutFileName);
- doExifStreamIO(jpegStream, s);
- s.flush();
- } catch (IOException e) {
- closeSilently(s);
- throw e;
- }
- s.close();
- }
-
- /**
- * Writes the tags from this ExifInterface object into a jpeg file, removing
- * prior exif tags.
- *
- * @param jpegFileName a String containing the filepath for a jpeg file.
- * @param exifOutFileName a String containing the filepath to which the jpeg
- * image with added exif tags will be written.
- * @throws java.io.FileNotFoundException
- * @throws java.io.IOException
- */
- public void writeExif(String jpegFileName, String exifOutFileName)
- throws FileNotFoundException, IOException {
- if (jpegFileName == null || exifOutFileName == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- InputStream is = null;
- try {
- is = new FileInputStream(jpegFileName);
- writeExif(is, exifOutFileName);
- } catch (IOException e) {
- closeSilently(is);
- throw e;
- }
- is.close();
- }
-
- /**
- * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this
- * ExifInterface object will be added to a jpeg image written to this
- * stream, removing prior exif tags. Other methods of this ExifInterface
- * object should not be called until the returned OutputStream has been
- * closed.
- *
- * @param outStream an OutputStream to wrap.
- * @return an OutputStream that wraps the outStream parameter, and adds exif
- * metadata. A jpeg image should be written to this stream.
- */
- public OutputStream getExifWriterStream(OutputStream outStream) {
- if (outStream == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- ExifOutputStream eos = new ExifOutputStream(outStream, this);
- eos.setExifData(mData);
- return eos;
- }
-
- /**
- * Returns an OutputStream object that writes to a file. Exif tags in this
- * ExifInterface object will be added to a jpeg image written to this
- * stream, removing prior exif tags. Other methods of this ExifInterface
- * object should not be called until the returned OutputStream has been
- * closed.
- *
- * @param exifOutFileName an String containing a filepath for a jpeg file.
- * @return an OutputStream that writes to the exifOutFileName file, and adds
- * exif metadata. A jpeg image should be written to this stream.
- * @throws java.io.FileNotFoundException
- */
- public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException {
- if (exifOutFileName == null) {
- throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
- }
- OutputStream out = null;
- try {
- out = new FileOutputStream(exifOutFileName);
- } catch (FileNotFoundException e) {
- closeSilently(out);
- throw e;
- }
- return getExifWriterStream(out);
- }
-
- /**
- * Attempts to do an in-place rewrite the exif metadata in a file for the
- * given tags. If tags do not exist or do not have the same size as the
- * existing exif tags, this method will fail.
- *
- * @param filename a String containing a filepath for a jpeg file with exif
- * tags to rewrite.
- * @param tags tags that will be written into the jpeg file over existing
- * tags if possible.
- * @return true if success, false if could not overwrite. If false, no
- * changes are made to the file.
- * @throws java.io.FileNotFoundException
- * @throws java.io.IOException
- */
- public boolean rewriteExif(String filename, Collection<ExifTag> tags)
- throws FileNotFoundException, IOException {
- RandomAccessFile file = null;
- InputStream is = null;
- boolean ret;
- try {
- File temp = new File(filename);
- is = new BufferedInputStream(new FileInputStream(temp));
-
- // Parse beginning of APP1 in exif to find size of exif header.
- ExifParser parser = null;
- try {
- parser = ExifParser.parse(is, this);
- } catch (ExifInvalidFormatException e) {
- throw new IOException("Invalid exif format : ", e);
- }
- long exifSize = parser.getOffsetToExifEndFromSOF();
-
- // Free up resources
- is.close();
- is = null;
-
- // Open file for memory mapping.
- file = new RandomAccessFile(temp, "rw");
- long fileLength = file.length();
- if (fileLength < exifSize) {
- throw new IOException("Filesize changed during operation");
- }
-
- // Map only exif header into memory.
- ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize);
-
- // Attempt to overwrite tag values without changing lengths (avoids
- // file copy).
- ret = rewriteExif(buf, tags);
- } catch (IOException e) {
- closeSilently(file);
- throw e;
- } finally {
- closeSilently(is);
- }
- file.close();
- return ret;
- }
-
- /**
- * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for
- * the given tags. If tags do not exist or do not have the same size as the
- * existing exif tags, this method will fail.
- *
- * @param buf a ByteBuffer containing a jpeg file with existing exif tags to
- * rewrite.
- * @param tags tags that will be written into the jpeg ByteBuffer over
- * existing tags if possible.
- * @return true if success, false if could not overwrite. If false, no
- * changes are made to the ByteBuffer.
- * @throws java.io.IOException
- */
- public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException {
- ExifModifier mod = null;
- try {
- mod = new ExifModifier(buf, this);
- for (ExifTag t : tags) {
- mod.modifyTag(t);
- }
- return mod.commit();
- } catch (ExifInvalidFormatException e) {
- throw new IOException("Invalid exif format : " + e);
- }
- }
-
- /**
- * Attempts to do an in-place rewrite of the exif metadata. If this fails,
- * fall back to overwriting file. This preserves tags that are not being
- * rewritten.
- *
- * @param filename a String containing a filepath for a jpeg file.
- * @param tags tags that will be written into the jpeg file over existing
- * tags if possible.
- * @throws java.io.FileNotFoundException
- * @throws java.io.IOException
- * @see #rewriteExif
- */
- public void forceRewriteExif(String filename, Collection<ExifTag> tags)
- throws FileNotFoundException,
- IOException {
- // Attempt in-place write
- if (!rewriteExif(filename, tags)) {
- // Fall back to doing a copy
- ExifData tempData = mData;
- mData = new ExifData(DEFAULT_BYTE_ORDER);
- FileInputStream is = null;
- ByteArrayOutputStream bytes = null;
- try {
- is = new FileInputStream(filename);
- bytes = new ByteArrayOutputStream();
- doExifStreamIO(is, bytes);
- byte[] imageBytes = bytes.toByteArray();
- readExif(imageBytes);
- setTags(tags);
- writeExif(imageBytes, filename);
- } catch (IOException e) {
- closeSilently(is);
- throw e;
- } finally {
- is.close();
- // Prevent clobbering of mData
- mData = tempData;
- }
- }
- }
-
- /**
- * Attempts to do an in-place rewrite of the exif metadata using the tags in
- * this ExifInterface object. If this fails, fall back to overwriting file.
- * This preserves tags that are not being rewritten.
- *
- * @param filename a String containing a filepath for a jpeg file.
- * @throws java.io.FileNotFoundException
- * @throws java.io.IOException
- * @see #rewriteExif
- */
- public void forceRewriteExif(String filename) throws FileNotFoundException, IOException {
- forceRewriteExif(filename, getAllTags());
- }
-
- /**
- * Get the exif tags in this ExifInterface object or null if none exist.
- *
- * @return a List of {@link ExifTag}s.
- */
- public List<ExifTag> getAllTags() {
- return mData.getAllTags();
- }
-
- /**
- * Returns a list of ExifTags that share a TID (which can be obtained by
- * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
- * exist.
- *
- * @param tagId a TID as defined in the exif standard (or with
- * {@link #defineTag}).
- * @return a List of {@link ExifTag}s.
- */
- public List<ExifTag> getTagsForTagId(short tagId) {
- return mData.getAllTagsForTagId(tagId);
- }
-
- /**
- * Returns a list of ExifTags that share an IFD (which can be obtained by
- * calling {@link #getTrueIFD} on a defined tag constant) or null if none
- * exist.
- *
- * @param ifdId an IFD as defined in the exif standard (or with
- * {@link #defineTag}).
- * @return a List of {@link ExifTag}s.
- */
- public List<ExifTag> getTagsForIfdId(int ifdId) {
- return mData.getAllTagsForIfd(ifdId);
- }
-
- /**
- * Gets an ExifTag for an IFD other than the tag's default.
- *
- * @see #getTag
- */
- public ExifTag getTag(int tagId, int ifdId) {
- if (!ExifTag.isValidIfd(ifdId)) {
- return null;
- }
- return mData.getTag(getTrueTagKey(tagId), ifdId);
- }
-
- /**
- * Returns the ExifTag in that tag's default IFD for a defined tag constant
- * or null if none exists.
- *
- * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @return an {@link ExifTag} or null if none exists.
- */
- public ExifTag getTag(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTag(tagId, ifdId);
- }
-
- /**
- * Gets a tag value for an IFD other than the tag's default.
- *
- * @see #getTagValue
- */
- public Object getTagValue(int tagId, int ifdId) {
- ExifTag t = getTag(tagId, ifdId);
- return (t == null) ? null : t.getValue();
- }
-
- /**
- * Returns the value of the ExifTag in that tag's default IFD for a defined
- * tag constant or null if none exists or the value could not be cast into
- * the return type.
- *
- * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @return the value of the ExifTag or null if none exists.
- */
- public Object getTagValue(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagValue(tagId, ifdId);
- }
-
- /*
- * Getter methods that are similar to getTagValue. Null is returned if the
- * tag value cannot be cast into the return type.
- */
-
- /**
- * @see #getTagValue
- */
- public String getTagStringValue(int tagId, int ifdId) {
- ExifTag t = getTag(tagId, ifdId);
- if (t == null) {
- return null;
- }
- return t.getValueAsString();
- }
-
- /**
- * @see #getTagValue
- */
- public String getTagStringValue(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagStringValue(tagId, ifdId);
- }
-
- /**
- * @see #getTagValue
- */
- public Long getTagLongValue(int tagId, int ifdId) {
- long[] l = getTagLongValues(tagId, ifdId);
- if (l == null || l.length <= 0) {
- return null;
- }
- return new Long(l[0]);
- }
-
- /**
- * @see #getTagValue
- */
- public Long getTagLongValue(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagLongValue(tagId, ifdId);
- }
-
- /**
- * @see #getTagValue
- */
- public Integer getTagIntValue(int tagId, int ifdId) {
- int[] l = getTagIntValues(tagId, ifdId);
- if (l == null || l.length <= 0) {
- return null;
- }
- return new Integer(l[0]);
- }
-
- /**
- * @see #getTagValue
- */
- public Integer getTagIntValue(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagIntValue(tagId, ifdId);
- }
-
- /**
- * @see #getTagValue
- */
- public Byte getTagByteValue(int tagId, int ifdId) {
- byte[] l = getTagByteValues(tagId, ifdId);
- if (l == null || l.length <= 0) {
- return null;
- }
- return new Byte(l[0]);
- }
-
- /**
- * @see #getTagValue
- */
- public Byte getTagByteValue(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagByteValue(tagId, ifdId);
- }
-
- /**
- * @see #getTagValue
- */
- public Rational getTagRationalValue(int tagId, int ifdId) {
- Rational[] l = getTagRationalValues(tagId, ifdId);
- if (l == null || l.length == 0) {
- return null;
- }
- return new Rational(l[0]);
- }
-
- /**
- * @see #getTagValue
- */
- public Rational getTagRationalValue(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagRationalValue(tagId, ifdId);
- }
-
- /**
- * @see #getTagValue
- */
- public long[] getTagLongValues(int tagId, int ifdId) {
- ExifTag t = getTag(tagId, ifdId);
- if (t == null) {
- return null;
- }
- return t.getValueAsLongs();
- }
-
- /**
- * @see #getTagValue
- */
- public long[] getTagLongValues(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagLongValues(tagId, ifdId);
- }
-
- /**
- * @see #getTagValue
- */
- public int[] getTagIntValues(int tagId, int ifdId) {
- ExifTag t = getTag(tagId, ifdId);
- if (t == null) {
- return null;
- }
- return t.getValueAsInts();
- }
-
- /**
- * @see #getTagValue
- */
- public int[] getTagIntValues(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagIntValues(tagId, ifdId);
- }
-
- /**
- * @see #getTagValue
- */
- public byte[] getTagByteValues(int tagId, int ifdId) {
- ExifTag t = getTag(tagId, ifdId);
- if (t == null) {
- return null;
- }
- return t.getValueAsBytes();
- }
-
- /**
- * @see #getTagValue
- */
- public byte[] getTagByteValues(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagByteValues(tagId, ifdId);
- }
-
- /**
- * @see #getTagValue
- */
- public Rational[] getTagRationalValues(int tagId, int ifdId) {
- ExifTag t = getTag(tagId, ifdId);
- if (t == null) {
- return null;
- }
- return t.getValueAsRationals();
- }
-
- /**
- * @see #getTagValue
- */
- public Rational[] getTagRationalValues(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return getTagRationalValues(tagId, ifdId);
- }
-
- /**
- * Checks whether a tag has a defined number of elements.
- *
- * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @return true if the tag has a defined number of elements.
- */
- public boolean isTagCountDefined(int tagId) {
- int info = getTagInfo().get(tagId);
- // No value in info can be zero, as all tags have a non-zero type
- if (info == 0) {
- return false;
- }
- return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
- }
-
- /**
- * Gets the defined number of elements for a tag.
- *
- * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
- * tag or the number of elements is not defined.
- */
- public int getDefinedTagCount(int tagId) {
- int info = getTagInfo().get(tagId);
- if (info == 0) {
- return ExifTag.SIZE_UNDEFINED;
- }
- return getComponentCountFromInfo(info);
- }
-
- /**
- * Gets the number of elements for an ExifTag in a given IFD.
- *
- * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @param ifdId the IFD containing the ExifTag to check.
- * @return the number of elements in the ExifTag, if the tag's size is
- * undefined this will return the actual number of elements that is
- * in the ExifTag's value.
- */
- public int getActualTagCount(int tagId, int ifdId) {
- ExifTag t = getTag(tagId, ifdId);
- if (t == null) {
- return 0;
- }
- return t.getComponentCount();
- }
-
- /**
- * Gets the default IFD for a tag.
- *
- * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
- * definition exists.
- */
- public int getDefinedTagDefaultIfd(int tagId) {
- int info = getTagInfo().get(tagId);
- if (info == DEFINITION_NULL) {
- return IFD_NULL;
- }
- return getTrueIfd(tagId);
- }
-
- /**
- * Gets the defined type for a tag.
- *
- * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @return the type.
- * @see ExifTag#getDataType()
- */
- public short getDefinedTagType(int tagId) {
- int info = getTagInfo().get(tagId);
- if (info == 0) {
- return -1;
- }
- return getTypeFromInfo(info);
- }
-
- /**
- * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD},
- * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT},
- * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD}
- * <p>
- * Note: defining tags with these TID's is disallowed.
- *
- * @param tag a tag's TID (can be obtained from a defined tag constant with
- * {@link #getTrueTagKey}).
- * @return true if the TID is that of an offset tag.
- */
- protected static boolean isOffsetTag(short tag) {
- return sOffsetTags.contains(tag);
- }
-
- /**
- * Creates a tag for a defined tag constant in a given IFD if that IFD is
- * allowed for the tag. This method will fail anytime the appropriate
- * {@link ExifTag#setValue} for this tag's datatype would fail.
- *
- * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @param ifdId the IFD that the tag should be in.
- * @param val the value of the tag to set.
- * @return an ExifTag object or null if one could not be constructed.
- * @see #buildTag
- */
- public ExifTag buildTag(int tagId, int ifdId, Object val) {
- int info = getTagInfo().get(tagId);
- if (info == 0 || val == null) {
- return null;
- }
- short type = getTypeFromInfo(info);
- int definedCount = getComponentCountFromInfo(info);
- boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
- if (!ExifInterface.isIfdAllowed(info, ifdId)) {
- return null;
- }
- ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
- if (!t.setValue(val)) {
- return null;
- }
- return t;
- }
-
- /**
- * Creates a tag for a defined tag constant in the tag's default IFD.
- *
- * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @param val the tag's value.
- * @return an ExifTag object.
- */
- public ExifTag buildTag(int tagId, Object val) {
- int ifdId = getTrueIfd(tagId);
- return buildTag(tagId, ifdId, val);
- }
-
- protected ExifTag buildUninitializedTag(int tagId) {
- int info = getTagInfo().get(tagId);
- if (info == 0) {
- return null;
- }
- short type = getTypeFromInfo(info);
- int definedCount = getComponentCountFromInfo(info);
- boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
- int ifdId = getTrueIfd(tagId);
- ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
- return t;
- }
-
- /**
- * Sets the value of an ExifTag if it exists in the given IFD. The value
- * must be the correct type and length for that ExifTag.
- *
- * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @param ifdId the IFD that the ExifTag is in.
- * @param val the value to set.
- * @return true if success, false if the ExifTag doesn't exist or the value
- * is the wrong type/length.
- * @see #setTagValue
- */
- public boolean setTagValue(int tagId, int ifdId, Object val) {
- ExifTag t = getTag(tagId, ifdId);
- if (t == null) {
- return false;
- }
- return t.setValue(val);
- }
-
- /**
- * Sets the value of an ExifTag if it exists it's default IFD. The value
- * must be the correct type and length for that ExifTag.
- *
- * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @param val the value to set.
- * @return true if success, false if the ExifTag doesn't exist or the value
- * is the wrong type/length.
- */
- public boolean setTagValue(int tagId, Object val) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- return setTagValue(tagId, ifdId, val);
- }
-
- /**
- * Puts an ExifTag into this ExifInterface object's tags, removing a
- * previous ExifTag with the same TID and IFD. The IFD it is put into will
- * be the one the tag was created with in {@link #buildTag}.
- *
- * @param tag an ExifTag to put into this ExifInterface's tags.
- * @return the previous ExifTag with the same TID and IFD or null if none
- * exists.
- */
- public ExifTag setTag(ExifTag tag) {
- return mData.addTag(tag);
- }
-
- /**
- * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
- * previous ExifTags with the same TID and IFDs will be removed.
- *
- * @param tags a Collection of ExifTags.
- * @see #setTag
- */
- public void setTags(Collection<ExifTag> tags) {
- for (ExifTag t : tags) {
- setTag(t);
- }
- }
-
- /**
- * Removes the ExifTag for a tag constant from the given IFD.
- *
- * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- * @param ifdId the IFD of the ExifTag to remove.
- */
- public void deleteTag(int tagId, int ifdId) {
- mData.removeTag(getTrueTagKey(tagId), ifdId);
- }
-
- /**
- * Removes the ExifTag for a tag constant from that tag's default IFD.
- *
- * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- */
- public void deleteTag(int tagId) {
- int ifdId = getDefinedTagDefaultIfd(tagId);
- deleteTag(tagId, ifdId);
- }
-
- /**
- * Creates a new tag definition in this ExifInterface object for a given TID
- * and default IFD. Creating a definition with the same TID and default IFD
- * as a previous definition will override it.
- *
- * @param tagId the TID for the tag.
- * @param defaultIfd the default IFD for the tag.
- * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
- * @param defaultComponentCount the number of elements of this tag's type in
- * the tags value.
- * @param allowedIfds the IFD's this tag is allowed to be put in.
- * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
- * {@link #TAG_NULL} if the definition could not be made.
- */
- public int setTagDefinition(short tagId, int defaultIfd, short tagType,
- short defaultComponentCount, int[] allowedIfds) {
- if (sBannedDefines.contains(tagId)) {
- return TAG_NULL;
- }
- if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
- int tagDef = defineTag(defaultIfd, tagId);
- if (tagDef == TAG_NULL) {
- return TAG_NULL;
- }
- int[] otherDefs = getTagDefinitionsForTagId(tagId);
- SparseIntArray infos = getTagInfo();
- // Make sure defaultIfd is in allowedIfds
- boolean defaultCheck = false;
- for (int i : allowedIfds) {
- if (defaultIfd == i) {
- defaultCheck = true;
- }
- if (!ExifTag.isValidIfd(i)) {
- return TAG_NULL;
- }
- }
- if (!defaultCheck) {
- return TAG_NULL;
- }
-
- int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
- // Make sure no identical tags can exist in allowedIfds
- if (otherDefs != null) {
- for (int def : otherDefs) {
- int tagInfo = infos.get(def);
- int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
- if ((ifdFlags & allowedFlags) != 0) {
- return TAG_NULL;
- }
- }
- }
- getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
- return tagDef;
- }
- return TAG_NULL;
- }
-
- protected int getTagDefinition(short tagId, int defaultIfd) {
- return getTagInfo().get(defineTag(defaultIfd, tagId));
- }
-
- protected int[] getTagDefinitionsForTagId(short tagId) {
- int[] ifds = IfdData.getIfds();
- int[] defs = new int[ifds.length];
- int counter = 0;
- SparseIntArray infos = getTagInfo();
- for (int i : ifds) {
- int def = defineTag(i, tagId);
- if (infos.get(def) != DEFINITION_NULL) {
- defs[counter++] = def;
- }
- }
- if (counter == 0) {
- return null;
- }
-
- return Arrays.copyOfRange(defs, 0, counter);
- }
-
- protected int getTagDefinitionForTag(ExifTag tag) {
- short type = tag.getDataType();
- int count = tag.getComponentCount();
- int ifd = tag.getIfd();
- return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
- }
-
- protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
- int[] defs = getTagDefinitionsForTagId(tagId);
- if (defs == null) {
- return TAG_NULL;
- }
- SparseIntArray infos = getTagInfo();
- int ret = TAG_NULL;
- for (int i : defs) {
- int info = infos.get(i);
- short defType = getTypeFromInfo(info);
- int defCount = getComponentCountFromInfo(info);
- int[] defIfds = getAllowedIfdsFromInfo(info);
- boolean validIfd = false;
- for (int j : defIfds) {
- if (j == ifd) {
- validIfd = true;
- break;
- }
- }
- if (validIfd && type == defType
- && (count == defCount || defCount == ExifTag.SIZE_UNDEFINED)) {
- ret = i;
- break;
- }
- }
- return ret;
- }
-
- /**
- * Removes a tag definition for given defined tag constant.
- *
- * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
- */
- public void removeTagDefinition(int tagId) {
- getTagInfo().delete(tagId);
- }
-
- /**
- * Resets tag definitions to the default ones.
- */
- public void resetTagDefinitions() {
- mTagInfo = null;
- }
-
- /**
- * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
- *
- * @return the thumbnail as a bitmap.
- */
- public Bitmap getThumbnailBitmap() {
- if (mData.hasCompressedThumbnail()) {
- byte[] thumb = mData.getCompressedThumbnail();
- return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
- } else if (mData.hasUncompressedStrip()) {
- // TODO: implement uncompressed
- }
- return null;
- }
-
- /**
- * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
- * The bytes may either be an uncompressed strip as specified in the exif
- * standard or a jpeg compressed image.
- *
- * @return the thumbnail as a byte array.
- */
- public byte[] getThumbnailBytes() {
- if (mData.hasCompressedThumbnail()) {
- return mData.getCompressedThumbnail();
- } else if (mData.hasUncompressedStrip()) {
- // TODO: implement this
- }
- return null;
- }
-
- /**
- * Returns the thumbnail if it is jpeg compressed, or null if none exists.
- *
- * @return the thumbnail as a byte array.
- */
- public byte[] getThumbnail() {
- return mData.getCompressedThumbnail();
- }
-
- /**
- * Check if thumbnail is compressed.
- *
- * @return true if the thumbnail is compressed.
- */
- public boolean isThumbnailCompressed() {
- return mData.hasCompressedThumbnail();
- }
-
- /**
- * Check if thumbnail exists.
- *
- * @return true if a compressed thumbnail exists.
- */
- public boolean hasThumbnail() {
- // TODO: add back in uncompressed strip
- return mData.hasCompressedThumbnail();
- }
-
- // TODO: uncompressed thumbnail setters
-
- /**
- * Sets the thumbnail to be a jpeg compressed image. Clears any prior
- * thumbnail.
- *
- * @param thumb a byte array containing a jpeg compressed image.
- * @return true if the thumbnail was set.
- */
- public boolean setCompressedThumbnail(byte[] thumb) {
- mData.clearThumbnailAndStrips();
- mData.setCompressedThumbnail(thumb);
- return true;
- }
-
- /**
- * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
- * thumbnail.
- *
- * @param thumb a bitmap to compress to a jpeg thumbnail.
- * @return true if the thumbnail was set.
- */
- public boolean setCompressedThumbnail(Bitmap thumb) {
- ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
- if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
- return false;
- }
- return setCompressedThumbnail(thumbnail.toByteArray());
- }
-
- /**
- * Clears the compressed thumbnail if it exists.
- */
- public void removeCompressedThumbnail() {
- mData.setCompressedThumbnail(null);
- }
-
- // Convenience methods:
-
- /**
- * Decodes the user comment tag into string as specified in the EXIF
- * standard. Returns null if decoding failed.
- */
- public String getUserComment() {
- return mData.getUserComment();
- }
-
- /**
- * Returns the Orientation ExifTag value for a given number of degrees.
- *
- * @param degrees the amount an image is rotated in degrees.
- */
- public static short getOrientationValueForRotation(int degrees) {
- degrees %= 360;
- if (degrees < 0) {
- degrees += 360;
- }
- if (degrees < 90) {
- return Orientation.TOP_LEFT; // 0 degrees
- } else if (degrees < 180) {
- return Orientation.RIGHT_TOP; // 90 degrees cw
- } else if (degrees < 270) {
- return Orientation.BOTTOM_LEFT; // 180 degrees
- } else {
- return Orientation.RIGHT_BOTTOM; // 270 degrees cw
- }
- }
-
- /**
- * Returns the rotation degrees corresponding to an ExifTag Orientation
- * value.
- *
- * @param orientation the ExifTag Orientation value.
- */
- public static int getRotationForOrientationValue(short orientation) {
- switch (orientation) {
- case Orientation.TOP_LEFT:
- return 0;
- case Orientation.RIGHT_TOP:
- return 90;
- case Orientation.BOTTOM_LEFT:
- return 180;
- case Orientation.RIGHT_BOTTOM:
- return 270;
- default:
- return 0;
- }
- }
-
- public static OrientationParams getOrientationParams(int orientation) {
- OrientationParams params = new OrientationParams();
- switch (orientation) {
- case Orientation.TOP_RIGHT: // Flip horizontal
- params.scaleX = -1;
- break;
- case Orientation.BOTTOM_RIGHT: // Flip vertical
- params.scaleY = -1;
- break;
- case Orientation.BOTTOM_LEFT: // Rotate 180
- params.rotation = 180;
- break;
- case Orientation.RIGHT_BOTTOM: // Rotate 270
- params.rotation = 270;
- params.invertDimensions = true;
- break;
- case Orientation.RIGHT_TOP: // Rotate 90
- params.rotation = 90;
- params.invertDimensions = true;
- break;
- case Orientation.LEFT_TOP: // Transpose
- params.rotation = 90;
- params.scaleX = -1;
- params.invertDimensions = true;
- break;
- case Orientation.LEFT_BOTTOM: // Transverse
- params.rotation = 270;
- params.scaleX = -1;
- params.invertDimensions = true;
- break;
- }
- return params;
- }
-
- public static class OrientationParams {
- public int rotation = 0;
- public int scaleX = 1;
- public int scaleY = 1;
- public boolean invertDimensions = false;
- }
-
- /**
- * Gets the double representation of the GPS latitude or longitude
- * coordinate.
- *
- * @param coordinate an array of 3 Rationals representing the degrees,
- * minutes, and seconds of the GPS location as defined in the
- * exif specification.
- * @param reference a GPS reference reperesented by a String containing "N",
- * "S", "E", or "W".
- * @return the GPS coordinate represented as degrees + minutes/60 +
- * seconds/3600
- */
- public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
- try {
- double degrees = coordinate[0].toDouble();
- double minutes = coordinate[1].toDouble();
- double seconds = coordinate[2].toDouble();
- double result = degrees + minutes / 60.0 + seconds / 3600.0;
- if ((reference.equals("S") || reference.equals("W"))) {
- return -result;
- }
- return result;
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new IllegalArgumentException();
- }
- }
-
- /**
- * Gets the GPS latitude and longitude as a pair of doubles from this
- * ExifInterface object's tags, or null if the necessary tags do not exist.
- *
- * @return an array of 2 doubles containing the latitude, and longitude
- * respectively.
- * @see #convertLatOrLongToDouble
- */
- public double[] getLatLongAsDoubles() {
- Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
- String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
- Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
- String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
- if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null
- || latitude.length < 3 || longitude.length < 3) {
- return null;
- }
- double[] latLon = new double[2];
- latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
- latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
- return latLon;
- }
-
- private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
- private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
- private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
- private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
- private final Calendar mGPSTimeStampCalendar = Calendar
- .getInstance(TimeZone.getTimeZone("UTC"));
-
- /**
- * Creates, formats, and sets the DateTimeStamp tag for one of:
- * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
- * {@link #TAG_DATE_TIME_ORIGINAL}.
- *
- * @param tagId one of the DateTimeStamp tags.
- * @param timestamp a timestamp to format.
- * @param timezone a TimeZone object.
- * @return true if success, false if the tag could not be set.
- */
- public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
- if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED
- || tagId == TAG_DATE_TIME_ORIGINAL) {
- mDateTimeStampFormat.setTimeZone(timezone);
- ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
- if (t == null) {
- return false;
- }
- setTag(t);
- } else {
- return false;
- }
- return true;
- }
-
- /**
- * Creates and sets all to the GPS tags for a give latitude and longitude.
- *
- * @param latitude a GPS latitude coordinate.
- * @param longitude a GPS longitude coordinate.
- * @return true if success, false if they could not be created or set.
- */
- public boolean addGpsTags(double latitude, double longitude) {
- ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
- ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
- ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF,
- latitude >= 0 ? GpsLatitudeRef.NORTH
- : GpsLatitudeRef.SOUTH);
- ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF,
- longitude >= 0 ? GpsLongitudeRef.EAST
- : GpsLongitudeRef.WEST);
- if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
- return false;
- }
- setTag(latTag);
- setTag(longTag);
- setTag(latRefTag);
- setTag(longRefTag);
- return true;
- }
-
- /**
- * Creates and sets the GPS timestamp tag.
- *
- * @param timestamp a GPS timestamp.
- * @return true if success, false if could not be created or set.
- */
- public boolean addGpsDateTimeStampTag(long timestamp) {
- ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
- if (t == null) {
- return false;
- }
- setTag(t);
- mGPSTimeStampCalendar.setTimeInMillis(timestamp);
- t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] {
- new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
- new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
- new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)
- });
- if (t == null) {
- return false;
- }
- setTag(t);
- return true;
- }
-
- private static Rational[] toExifLatLong(double value) {
- // convert to the format dd/1 mm/1 ssss/100
- value = Math.abs(value);
- int degrees = (int) value;
- value = (value - degrees) * 60;
- int minutes = (int) value;
- value = (value - minutes) * 6000;
- int seconds = (int) value;
- return new Rational[] {
- new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)
- };
- }
-
- private void doExifStreamIO(InputStream is, OutputStream os) throws IOException {
- byte[] buf = new byte[1024];
- int ret = is.read(buf, 0, 1024);
- while (ret != -1) {
- os.write(buf, 0, ret);
- ret = is.read(buf, 0, 1024);
- }
- }
-
- protected static void closeSilently(Closeable c) {
- if (c != null) {
- try {
- c.close();
- } catch (Throwable e) {
- // ignored
- }
- }
- }
-
- private SparseIntArray mTagInfo = null;
-
- protected SparseIntArray getTagInfo() {
- if (mTagInfo == null) {
- mTagInfo = new SparseIntArray();
- initTagInfo();
- }
- return mTagInfo;
- }
-
- private void initTagInfo() {
- /**
- * We put tag information in a 4-bytes integer. The first byte a bitmask
- * representing the allowed IFDs of the tag, the second byte is the data
- * type, and the last two byte are a short value indicating the default
- * component count of this tag.
- */
- // IFD0 tags
- int[] ifdAllowedIfds = {
- IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1
- };
- int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
- mTagInfo.put(ExifInterface.TAG_MAKE,
- ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH,
- ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH,
- ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
- mTagInfo.put(ExifInterface.TAG_COMPRESSION,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16
- | 1);
- mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_X_RESOLUTION,
- ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION,
- ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS,
- ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP,
- ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS,
- ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION,
- ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
- mTagInfo.put(ExifInterface.TAG_WHITE_POINT,
- ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES,
- ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
- mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
- ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
- mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE,
- ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
- mTagInfo.put(ExifInterface.TAG_DATE_TIME,
- ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
- mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION,
- ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_MAKE,
- ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_MODEL,
- ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_SOFTWARE,
- ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_ARTIST,
- ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_COPYRIGHT,
- ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_EXIF_IFD,
- ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_IFD,
- ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- // IFD1 tags
- int[] ifd1AllowedIfds = {
- IfdId.TYPE_IFD_1
- };
- int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
- mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
- ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
- ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- // Exif tags
- int[] exifAllowedIfds = {
- IfdId.TYPE_IFD_EXIF
- };
- int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
- mTagInfo.put(ExifInterface.TAG_EXIF_VERSION,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
- mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
- mTagInfo.put(ExifInterface.TAG_COLOR_SPACE,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
- mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION,
- exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION,
- exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_MAKER_NOTE,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_USER_COMMENT,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE,
- exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
- mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL,
- exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
- mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED,
- exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
- mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME,
- exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL,
- exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED,
- exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID,
- exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
- mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_F_NUMBER,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY,
- exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_OECF,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE,
- exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE,
- exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
- exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_METERING_MODE,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_FLASH,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SENSING_METHOD,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_FILE_SOURCE,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SCENE_TYPE,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_CFA_PATTERN,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL,
- exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_CONTRAST,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SATURATION,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_SHARPNESS,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
- exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
- exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags
- | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
- // GPS tag
- int[] gpsAllowedIfds = {
- IfdId.TYPE_IFD_GPS
- };
- int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
- mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID,
- gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
- mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE,
- gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
- mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE,
- gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
- mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF,
- gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
- mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_GPS_STATUS,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_DOP,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_SPEED,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_TRACK,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
- mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE,
- gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
- mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD,
- gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION,
- gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
- mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP,
- gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
- mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL,
- gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
- // Interoperability tag
- int[] interopAllowedIfds = {
- IfdId.TYPE_IFD_INTEROPERABILITY
- };
- int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
- mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16
- | ExifTag.SIZE_UNDEFINED);
- }
-
- protected static int getAllowedIfdFlagsFromInfo(int info) {
- return info >>> 24;
- }
-
- protected static int[] getAllowedIfdsFromInfo(int info) {
- int ifdFlags = getAllowedIfdFlagsFromInfo(info);
- int[] ifds = IfdData.getIfds();
- ArrayList<Integer> l = new ArrayList<Integer>();
- for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
- int flag = (ifdFlags >> i) & 1;
- if (flag == 1) {
- l.add(ifds[i]);
- }
- }
- if (l.size() <= 0) {
- return null;
- }
- int[] ret = new int[l.size()];
- int j = 0;
- for (int i : l) {
- ret[j++] = i;
- }
- return ret;
- }
-
- protected static boolean isIfdAllowed(int info, int ifd) {
- int[] ifds = IfdData.getIfds();
- int ifdFlags = getAllowedIfdFlagsFromInfo(info);
- for (int i = 0; i < ifds.length; i++) {
- if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
- return true;
- }
- }
- return false;
- }
-
- protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
- if (allowedIfds == null || allowedIfds.length == 0) {
- return 0;
- }
- int flags = 0;
- int[] ifds = IfdData.getIfds();
- for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
- for (int j : allowedIfds) {
- if (ifds[i] == j) {
- flags |= 1 << i;
- break;
- }
- }
- }
- return flags;
- }
-
- protected static short getTypeFromInfo(int info) {
- return (short) ((info >> 16) & 0x0ff);
- }
-
- protected static int getComponentCountFromInfo(int info) {
- return info & 0x0ffff;
- }
-
-}
diff --git a/src/com/android/messaging/util/exif/ExifInvalidFormatException.java b/src/com/android/messaging/util/exif/ExifInvalidFormatException.java
deleted file mode 100644
index a38f8a3..0000000
--- a/src/com/android/messaging/util/exif/ExifInvalidFormatException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-public class ExifInvalidFormatException extends Exception {
- public ExifInvalidFormatException(String meg) {
- super(meg);
- }
-}
diff --git a/src/com/android/messaging/util/exif/ExifModifier.java b/src/com/android/messaging/util/exif/ExifModifier.java
deleted file mode 100644
index 274022c..0000000
--- a/src/com/android/messaging/util/exif/ExifModifier.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import android.util.Log;
-import com.android.messaging.util.LogUtil;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.List;
-
-class ExifModifier {
- public static final String TAG = LogUtil.BUGLE_TAG;
- public static final boolean DEBUG = false;
- private final ByteBuffer mByteBuffer;
- private final ExifData mTagToModified;
- private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
- private final ExifInterface mInterface;
- private int mOffsetBase;
-
- private static class TagOffset {
- final int mOffset;
- final ExifTag mTag;
-
- TagOffset(ExifTag tag, int offset) {
- mTag = tag;
- mOffset = offset;
- }
- }
-
- protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
- ExifInvalidFormatException {
- mByteBuffer = byteBuffer;
- mOffsetBase = byteBuffer.position();
- mInterface = iRef;
- InputStream is = null;
- try {
- is = new ByteBufferInputStream(byteBuffer);
- // Do not require any IFD;
- ExifParser parser = ExifParser.parse(is, mInterface);
- mTagToModified = new ExifData(parser.getByteOrder());
- mOffsetBase += parser.getTiffStartPosition();
- mByteBuffer.position(0);
- } finally {
- ExifInterface.closeSilently(is);
- }
- }
-
- protected ByteOrder getByteOrder() {
- return mTagToModified.getByteOrder();
- }
-
- protected boolean commit() throws IOException, ExifInvalidFormatException {
- InputStream is = null;
- try {
- is = new ByteBufferInputStream(mByteBuffer);
- int flag = 0;
- IfdData[] ifdDatas = new IfdData[] {
- mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
- mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
- mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
- mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
- mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
- };
-
- if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
- flag |= ExifParser.OPTION_IFD_0;
- }
- if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
- flag |= ExifParser.OPTION_IFD_1;
- }
- if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
- flag |= ExifParser.OPTION_IFD_EXIF;
- }
- if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
- flag |= ExifParser.OPTION_IFD_GPS;
- }
- if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
- flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
- }
-
- ExifParser parser = ExifParser.parse(is, flag, mInterface);
- int event = parser.next();
- IfdData currIfd = null;
- while (event != ExifParser.EVENT_END) {
- switch (event) {
- case ExifParser.EVENT_START_OF_IFD:
- currIfd = ifdDatas[parser.getCurrentIfd()];
- if (currIfd == null) {
- parser.skipRemainingTagsInCurrentIfd();
- }
- break;
- case ExifParser.EVENT_NEW_TAG:
- ExifTag oldTag = parser.getTag();
- ExifTag newTag = currIfd.getTag(oldTag.getTagId());
- if (newTag != null) {
- if (newTag.getComponentCount() != oldTag.getComponentCount()
- || newTag.getDataType() != oldTag.getDataType()) {
- return false;
- } else {
- mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
- currIfd.removeTag(oldTag.getTagId());
- if (currIfd.getTagCount() == 0) {
- parser.skipRemainingTagsInCurrentIfd();
- }
- }
- }
- break;
- }
- event = parser.next();
- }
- for (IfdData ifd : ifdDatas) {
- if (ifd != null && ifd.getTagCount() > 0) {
- return false;
- }
- }
- modify();
- } finally {
- ExifInterface.closeSilently(is);
- }
- return true;
- }
-
- private void modify() {
- mByteBuffer.order(getByteOrder());
- for (TagOffset tagOffset : mTagOffsets) {
- writeTagValue(tagOffset.mTag, tagOffset.mOffset);
- }
- }
-
- private void writeTagValue(ExifTag tag, int offset) {
- if (DEBUG) {
- Log.v(TAG, "modifying tag to: \n" + tag.toString());
- Log.v(TAG, "at offset: " + offset);
- }
- mByteBuffer.position(offset + mOffsetBase);
- switch (tag.getDataType()) {
- case ExifTag.TYPE_ASCII:
- byte buf[] = tag.getStringByte();
- if (buf.length == tag.getComponentCount()) {
- buf[buf.length - 1] = 0;
- mByteBuffer.put(buf);
- } else {
- mByteBuffer.put(buf);
- mByteBuffer.put((byte) 0);
- }
- break;
- case ExifTag.TYPE_LONG:
- case ExifTag.TYPE_UNSIGNED_LONG:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- mByteBuffer.putInt((int) tag.getValueAt(i));
- }
- break;
- case ExifTag.TYPE_RATIONAL:
- case ExifTag.TYPE_UNSIGNED_RATIONAL:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- Rational v = tag.getRational(i);
- mByteBuffer.putInt((int) v.getNumerator());
- mByteBuffer.putInt((int) v.getDenominator());
- }
- break;
- case ExifTag.TYPE_UNDEFINED:
- case ExifTag.TYPE_UNSIGNED_BYTE:
- buf = new byte[tag.getComponentCount()];
- tag.getBytes(buf);
- mByteBuffer.put(buf);
- break;
- case ExifTag.TYPE_UNSIGNED_SHORT:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- mByteBuffer.putShort((short) tag.getValueAt(i));
- }
- break;
- }
- }
-
- public void modifyTag(ExifTag tag) {
- mTagToModified.addTag(tag);
- }
-}
diff --git a/src/com/android/messaging/util/exif/ExifOutputStream.java b/src/com/android/messaging/util/exif/ExifOutputStream.java
deleted file mode 100644
index 2016da4..0000000
--- a/src/com/android/messaging/util/exif/ExifOutputStream.java
+++ /dev/null
@@ -1,522 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import android.util.Log;
-import com.android.messaging.util.LogUtil;
-
-import java.io.BufferedOutputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-
-/**
- * This class provides a way to replace the Exif header of a JPEG image.
- * <p>
- * Below is an example of writing EXIF data into a file
- *
- * <pre>
- * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
- * OutputStream os = null;
- * try {
- * os = new FileOutputStream(path);
- * ExifOutputStream eos = new ExifOutputStream(os);
- * // Set the exif header
- * eos.setExifData(exif);
- * // Write the original jpeg out, the header will be add into the file.
- * eos.write(jpeg);
- * } catch (FileNotFoundException e) {
- * e.printStackTrace();
- * } catch (IOException e) {
- * e.printStackTrace();
- * } finally {
- * if (os != null) {
- * try {
- * os.close();
- * } catch (IOException e) {
- * e.printStackTrace();
- * }
- * }
- * }
- * }
- * </pre>
- */
-class ExifOutputStream extends FilterOutputStream {
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final boolean DEBUG = false;
- private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
-
- private static final int STATE_SOI = 0;
- private static final int STATE_FRAME_HEADER = 1;
- private static final int STATE_JPEG_DATA = 2;
-
- private static final int EXIF_HEADER = 0x45786966;
- private static final short TIFF_HEADER = 0x002A;
- private static final short TIFF_BIG_ENDIAN = 0x4d4d;
- private static final short TIFF_LITTLE_ENDIAN = 0x4949;
- private static final short TAG_SIZE = 12;
- private static final short TIFF_HEADER_SIZE = 8;
- private static final int MAX_EXIF_SIZE = 65535;
-
- private ExifData mExifData;
- private int mState = STATE_SOI;
- private int mByteToSkip;
- private int mByteToCopy;
- private final byte[] mSingleByteArray = new byte[1];
- private final ByteBuffer mBuffer = ByteBuffer.allocate(4);
- private final ExifInterface mInterface;
-
- protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
- super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
- mInterface = iRef;
- }
-
- /**
- * Sets the ExifData to be written into the JPEG file. Should be called
- * before writing image data.
- */
- protected void setExifData(ExifData exifData) {
- mExifData = exifData;
- }
-
- /**
- * Gets the Exif header to be written into the JPEF file.
- */
- protected ExifData getExifData() {
- return mExifData;
- }
-
- private int requestByteToBuffer(int requestByteCount, byte[] buffer
- , int offset, int length) {
- int byteNeeded = requestByteCount - mBuffer.position();
- int byteToRead = length > byteNeeded ? byteNeeded : length;
- mBuffer.put(buffer, offset, byteToRead);
- return byteToRead;
- }
-
- /**
- * Writes the image out. The input data should be a valid JPEG format. After
- * writing, it's Exif header will be replaced by the given header.
- */
- @Override
- public void write(byte[] buffer, int offset, int length) throws IOException {
- while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
- && length > 0) {
- if (mByteToSkip > 0) {
- int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
- length -= byteToProcess;
- mByteToSkip -= byteToProcess;
- offset += byteToProcess;
- }
- if (mByteToCopy > 0) {
- int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
- out.write(buffer, offset, byteToProcess);
- length -= byteToProcess;
- mByteToCopy -= byteToProcess;
- offset += byteToProcess;
- }
- if (length == 0) {
- return;
- }
- switch (mState) {
- case STATE_SOI:
- int byteRead = requestByteToBuffer(2, buffer, offset, length);
- offset += byteRead;
- length -= byteRead;
- if (mBuffer.position() < 2) {
- return;
- }
- mBuffer.rewind();
- if (mBuffer.getShort() != JpegHeader.SOI) {
- throw new IOException("Not a valid jpeg image, cannot write exif");
- }
- out.write(mBuffer.array(), 0, 2);
- mState = STATE_FRAME_HEADER;
- mBuffer.rewind();
- writeExifData();
- break;
- case STATE_FRAME_HEADER:
- // We ignore the APP1 segment and copy all other segments
- // until SOF tag.
- byteRead = requestByteToBuffer(4, buffer, offset, length);
- offset += byteRead;
- length -= byteRead;
- // Check if this image data doesn't contain SOF.
- if (mBuffer.position() == 2) {
- short tag = mBuffer.getShort();
- if (tag == JpegHeader.EOI) {
- out.write(mBuffer.array(), 0, 2);
- mBuffer.rewind();
- }
- }
- if (mBuffer.position() < 4) {
- return;
- }
- mBuffer.rewind();
- short marker = mBuffer.getShort();
- if (marker == JpegHeader.APP1) {
- mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
- mState = STATE_JPEG_DATA;
- } else if (!JpegHeader.isSofMarker(marker)) {
- out.write(mBuffer.array(), 0, 4);
- mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
- } else {
- out.write(mBuffer.array(), 0, 4);
- mState = STATE_JPEG_DATA;
- }
- mBuffer.rewind();
- }
- }
- if (length > 0) {
- out.write(buffer, offset, length);
- }
- }
-
- /**
- * Writes the one bytes out. The input data should be a valid JPEG format.
- * After writing, it's Exif header will be replaced by the given header.
- */
- @Override
- public void write(int oneByte) throws IOException {
- mSingleByteArray[0] = (byte) (0xff & oneByte);
- write(mSingleByteArray);
- }
-
- /**
- * Equivalent to calling write(buffer, 0, buffer.length).
- */
- @Override
- public void write(byte[] buffer) throws IOException {
- write(buffer, 0, buffer.length);
- }
-
- private void writeExifData() throws IOException {
- if (mExifData == null) {
- return;
- }
- if (DEBUG) {
- Log.v(TAG, "Writing exif data...");
- }
- ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
- createRequiredIfdAndTag();
- int exifSize = calculateAllOffset();
- if (exifSize + 8 > MAX_EXIF_SIZE) {
- throw new IOException("Exif header is too large (>64Kb)");
- }
- OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
- dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
- dataOutputStream.writeShort(JpegHeader.APP1);
- dataOutputStream.writeShort((short) (exifSize + 8));
- dataOutputStream.writeInt(EXIF_HEADER);
- dataOutputStream.writeShort((short) 0x0000);
- if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
- dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
- } else {
- dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
- }
- dataOutputStream.setByteOrder(mExifData.getByteOrder());
- dataOutputStream.writeShort(TIFF_HEADER);
- dataOutputStream.writeInt(8);
- writeAllTags(dataOutputStream);
- writeThumbnail(dataOutputStream);
- for (ExifTag t : nullTags) {
- mExifData.addTag(t);
- }
- }
-
- private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
- ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
- if (data.getAllTags() == null) {
- return nullTags;
- }
- for (ExifTag t : data.getAllTags()) {
- if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
- data.removeTag(t.getTagId(), t.getIfd());
- nullTags.add(t);
- }
- }
- return nullTags;
- }
-
- private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
- if (mExifData.hasCompressedThumbnail()) {
- dataOutputStream.write(mExifData.getCompressedThumbnail());
- } else if (mExifData.hasUncompressedStrip()) {
- for (int i = 0; i < mExifData.getStripCount(); i++) {
- dataOutputStream.write(mExifData.getStrip(i));
- }
- }
- }
-
- private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
- writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
- writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
- IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
- if (interoperabilityIfd != null) {
- writeIfd(interoperabilityIfd, dataOutputStream);
- }
- IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
- if (gpsIfd != null) {
- writeIfd(gpsIfd, dataOutputStream);
- }
- IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
- if (ifd1 != null) {
- writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
- }
- }
-
- private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
- throws IOException {
- ExifTag[] tags = ifd.getAllTags();
- dataOutputStream.writeShort((short) tags.length);
- for (ExifTag tag : tags) {
- dataOutputStream.writeShort(tag.getTagId());
- dataOutputStream.writeShort(tag.getDataType());
- dataOutputStream.writeInt(tag.getComponentCount());
- if (DEBUG) {
- Log.v(TAG, "\n" + tag.toString());
- }
- if (tag.getDataSize() > 4) {
- dataOutputStream.writeInt(tag.getOffset());
- } else {
- ExifOutputStream.writeTagValue(tag, dataOutputStream);
- for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
- dataOutputStream.write(0);
- }
- }
- }
- dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
- for (ExifTag tag : tags) {
- if (tag.getDataSize() > 4) {
- ExifOutputStream.writeTagValue(tag, dataOutputStream);
- }
- }
- }
-
- private int calculateOffsetOfIfd(IfdData ifd, int offset) {
- offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
- ExifTag[] tags = ifd.getAllTags();
- for (ExifTag tag : tags) {
- if (tag.getDataSize() > 4) {
- tag.setOffset(offset);
- offset += tag.getDataSize();
- }
- }
- return offset;
- }
-
- private void createRequiredIfdAndTag() throws IOException {
- // IFD0 is required for all file
- IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
- if (ifd0 == null) {
- ifd0 = new IfdData(IfdId.TYPE_IFD_0);
- mExifData.addIfdData(ifd0);
- }
- ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
- if (exifOffsetTag == null) {
- throw new IOException("No definition for crucial exif tag: "
- + ExifInterface.TAG_EXIF_IFD);
- }
- ifd0.setTag(exifOffsetTag);
-
- // Exif IFD is required for all files.
- IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
- if (exifIfd == null) {
- exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
- mExifData.addIfdData(exifIfd);
- }
-
- // GPS IFD
- IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
- if (gpsIfd != null) {
- ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
- if (gpsOffsetTag == null) {
- throw new IOException("No definition for crucial exif tag: "
- + ExifInterface.TAG_GPS_IFD);
- }
- ifd0.setTag(gpsOffsetTag);
- }
-
- // Interoperability IFD
- IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
- if (interIfd != null) {
- ExifTag interOffsetTag = mInterface
- .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
- if (interOffsetTag == null) {
- throw new IOException("No definition for crucial exif tag: "
- + ExifInterface.TAG_INTEROPERABILITY_IFD);
- }
- exifIfd.setTag(interOffsetTag);
- }
-
- IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
-
- // thumbnail
- if (mExifData.hasCompressedThumbnail()) {
-
- if (ifd1 == null) {
- ifd1 = new IfdData(IfdId.TYPE_IFD_1);
- mExifData.addIfdData(ifd1);
- }
-
- ExifTag offsetTag = mInterface
- .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
- if (offsetTag == null) {
- throw new IOException("No definition for crucial exif tag: "
- + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
- }
-
- ifd1.setTag(offsetTag);
- ExifTag lengthTag = mInterface
- .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
- if (lengthTag == null) {
- throw new IOException("No definition for crucial exif tag: "
- + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
- }
-
- lengthTag.setValue(mExifData.getCompressedThumbnail().length);
- ifd1.setTag(lengthTag);
-
- // Get rid of tags for uncompressed if they exist.
- ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
- ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
- } else if (mExifData.hasUncompressedStrip()) {
- if (ifd1 == null) {
- ifd1 = new IfdData(IfdId.TYPE_IFD_1);
- mExifData.addIfdData(ifd1);
- }
- int stripCount = mExifData.getStripCount();
- ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
- if (offsetTag == null) {
- throw new IOException("No definition for crucial exif tag: "
- + ExifInterface.TAG_STRIP_OFFSETS);
- }
- ExifTag lengthTag = mInterface
- .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
- if (lengthTag == null) {
- throw new IOException("No definition for crucial exif tag: "
- + ExifInterface.TAG_STRIP_BYTE_COUNTS);
- }
- long[] lengths = new long[stripCount];
- for (int i = 0; i < mExifData.getStripCount(); i++) {
- lengths[i] = mExifData.getStrip(i).length;
- }
- lengthTag.setValue(lengths);
- ifd1.setTag(offsetTag);
- ifd1.setTag(lengthTag);
- // Get rid of tags for compressed if they exist.
- ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
- ifd1.removeTag(ExifInterface
- .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
- } else if (ifd1 != null) {
- // Get rid of offset and length tags if there is no thumbnail.
- ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
- ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
- ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
- ifd1.removeTag(ExifInterface
- .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
- }
- }
-
- private int calculateAllOffset() {
- int offset = TIFF_HEADER_SIZE;
- IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
- offset = calculateOffsetOfIfd(ifd0, offset);
- ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
-
- IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
- offset = calculateOffsetOfIfd(exifIfd, offset);
-
- IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
- if (interIfd != null) {
- exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
- .setValue(offset);
- offset = calculateOffsetOfIfd(interIfd, offset);
- }
-
- IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
- if (gpsIfd != null) {
- ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
- offset = calculateOffsetOfIfd(gpsIfd, offset);
- }
-
- IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
- if (ifd1 != null) {
- ifd0.setOffsetToNextIfd(offset);
- offset = calculateOffsetOfIfd(ifd1, offset);
- }
-
- // thumbnail
- if (mExifData.hasCompressedThumbnail()) {
- ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
- .setValue(offset);
- offset += mExifData.getCompressedThumbnail().length;
- } else if (mExifData.hasUncompressedStrip()) {
- int stripCount = mExifData.getStripCount();
- long[] offsets = new long[stripCount];
- for (int i = 0; i < mExifData.getStripCount(); i++) {
- offsets[i] = offset;
- offset += mExifData.getStrip(i).length;
- }
- ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
- offsets);
- }
- return offset;
- }
-
- static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
- throws IOException {
- switch (tag.getDataType()) {
- case ExifTag.TYPE_ASCII:
- byte buf[] = tag.getStringByte();
- if (buf.length == tag.getComponentCount()) {
- buf[buf.length - 1] = 0;
- dataOutputStream.write(buf);
- } else {
- dataOutputStream.write(buf);
- dataOutputStream.write(0);
- }
- break;
- case ExifTag.TYPE_LONG:
- case ExifTag.TYPE_UNSIGNED_LONG:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeInt((int) tag.getValueAt(i));
- }
- break;
- case ExifTag.TYPE_RATIONAL:
- case ExifTag.TYPE_UNSIGNED_RATIONAL:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeRational(tag.getRational(i));
- }
- break;
- case ExifTag.TYPE_UNDEFINED:
- case ExifTag.TYPE_UNSIGNED_BYTE:
- buf = new byte[tag.getComponentCount()];
- tag.getBytes(buf);
- dataOutputStream.write(buf);
- break;
- case ExifTag.TYPE_UNSIGNED_SHORT:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeShort((short) tag.getValueAt(i));
- }
- break;
- }
- }
-}
diff --git a/src/com/android/messaging/util/exif/ExifParser.java b/src/com/android/messaging/util/exif/ExifParser.java
deleted file mode 100644
index 4b6cf68..0000000
--- a/src/com/android/messaging/util/exif/ExifParser.java
+++ /dev/null
@@ -1,918 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import android.util.Log;
-import com.android.messaging.util.LogUtil;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-/**
- * This class provides a low-level EXIF parsing API. Given a JPEG format
- * InputStream, the caller can request which IFD's to read via
- * {@link #parse(java.io.InputStream, int)} with given options.
- * <p>
- * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
- * parser.
- *
- * <pre>
- * void parse() {
- * ExifParser parser = ExifParser.parse(mImageInputStream,
- * ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
- * int event = parser.next();
- * while (event != ExifParser.EVENT_END) {
- * switch (event) {
- * case ExifParser.EVENT_START_OF_IFD:
- * break;
- * case ExifParser.EVENT_NEW_TAG:
- * ExifTag tag = parser.getTag();
- * if (!tag.hasValue()) {
- * parser.registerForTagValue(tag);
- * } else {
- * processTag(tag);
- * }
- * break;
- * case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
- * tag = parser.getTag();
- * if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
- * processTag(tag);
- * }
- * break;
- * }
- * event = parser.next();
- * }
- * }
- *
- * void processTag(ExifTag tag) {
- * // process the tag as you like.
- * }
- * </pre>
- */
-public class ExifParser {
- private static final boolean LOGV = false;
- private static final String TAG = LogUtil.BUGLE_TAG;
- /**
- * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
- * know which IFD we are in.
- */
- public static final int EVENT_START_OF_IFD = 0;
- /**
- * When the parser reaches a new tag. Call {@link #getTag()}to get the
- * corresponding tag.
- */
- public static final int EVENT_NEW_TAG = 1;
- /**
- * When the parser reaches the value area of tag that is registered by
- * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
- * to get the corresponding tag.
- */
- public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
-
- /**
- * When the parser reaches the compressed image area.
- */
- public static final int EVENT_COMPRESSED_IMAGE = 3;
- /**
- * When the parser reaches the uncompressed image strip. Call
- * {@link #getStripIndex()} to get the index of the strip.
- *
- * @see #getStripIndex()
- * @see #getStripCount()
- */
- public static final int EVENT_UNCOMPRESSED_STRIP = 4;
- /**
- * When there is nothing more to parse.
- */
- public static final int EVENT_END = 5;
-
- /**
- * Option bit to request to parse IFD0.
- */
- public static final int OPTION_IFD_0 = 1 << 0;
- /**
- * Option bit to request to parse IFD1.
- */
- public static final int OPTION_IFD_1 = 1 << 1;
- /**
- * Option bit to request to parse Exif-IFD.
- */
- public static final int OPTION_IFD_EXIF = 1 << 2;
- /**
- * Option bit to request to parse GPS-IFD.
- */
- public static final int OPTION_IFD_GPS = 1 << 3;
- /**
- * Option bit to request to parse Interoperability-IFD.
- */
- public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
- /**
- * Option bit to request to parse thumbnail.
- */
- public static final int OPTION_THUMBNAIL = 1 << 5;
-
- protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
- protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
-
- // TIFF header
- protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
- protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
- protected static final short TIFF_HEADER_TAIL = 0x002A;
-
- protected static final int TAG_SIZE = 12;
- protected static final int OFFSET_SIZE = 2;
-
- private static final Charset US_ASCII = Charset.forName("US-ASCII");
-
- protected static final int DEFAULT_IFD0_OFFSET = 8;
-
- private final CountedDataInputStream mTiffStream;
- private final int mOptions;
- private int mIfdStartOffset = 0;
- private int mNumOfTagInIfd = 0;
- private int mIfdType;
- private ExifTag mTag;
- private ImageEvent mImageEvent;
- private int mStripCount;
- private ExifTag mStripSizeTag;
- private ExifTag mJpegSizeTag;
- private boolean mNeedToParseOffsetsInCurrentIfd;
- private boolean mContainExifData = false;
- private int mApp1End;
- private int mOffsetToApp1EndFromSOF = 0;
- private byte[] mDataAboveIfd0;
- private int mIfd0Position;
- private int mTiffStartPosition;
- private final ExifInterface mInterface;
-
- private static final short TAG_EXIF_IFD = ExifInterface
- .getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
- private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
- private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
- .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
- private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
- .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
- private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
- .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
- private static final short TAG_STRIP_OFFSETS = ExifInterface
- .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
- private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
- .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
-
- private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
-
- private boolean isIfdRequested(int ifdType) {
- switch (ifdType) {
- case IfdId.TYPE_IFD_0:
- return (mOptions & OPTION_IFD_0) != 0;
- case IfdId.TYPE_IFD_1:
- return (mOptions & OPTION_IFD_1) != 0;
- case IfdId.TYPE_IFD_EXIF:
- return (mOptions & OPTION_IFD_EXIF) != 0;
- case IfdId.TYPE_IFD_GPS:
- return (mOptions & OPTION_IFD_GPS) != 0;
- case IfdId.TYPE_IFD_INTEROPERABILITY:
- return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
- }
- return false;
- }
-
- private boolean isThumbnailRequested() {
- return (mOptions & OPTION_THUMBNAIL) != 0;
- }
-
- private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
- throws IOException, ExifInvalidFormatException {
- if (inputStream == null) {
- throw new IOException("Null argument inputStream to ExifParser");
- }
- if (LOGV) {
- Log.v(TAG, "Reading exif...");
- }
- mInterface = iRef;
- mContainExifData = seekTiffData(inputStream);
- mTiffStream = new CountedDataInputStream(inputStream);
- mOptions = options;
- if (!mContainExifData) {
- return;
- }
-
- parseTiffHeader();
- long offset = mTiffStream.readUnsignedInt();
- if (offset > Integer.MAX_VALUE) {
- throw new ExifInvalidFormatException("Invalid offset " + offset);
- }
- mIfd0Position = (int) offset;
- mIfdType = IfdId.TYPE_IFD_0;
- if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
- registerIfd(IfdId.TYPE_IFD_0, offset);
- if (offset != DEFAULT_IFD0_OFFSET) {
- mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
- read(mDataAboveIfd0);
- }
- }
- }
-
- /**
- * Parses the the given InputStream with the given options
- *
- * @exception java.io.IOException
- * @exception ExifInvalidFormatException
- */
- protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
- throws IOException, ExifInvalidFormatException {
- return new ExifParser(inputStream, options, iRef);
- }
-
- /**
- * Parses the the given InputStream with default options; that is, every IFD
- * and thumbnaill will be parsed.
- *
- * @exception java.io.IOException
- * @exception ExifInvalidFormatException
- * @see #parse(java.io.InputStream, int)
- */
- protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
- throws IOException, ExifInvalidFormatException {
- return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
- | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
- | OPTION_THUMBNAIL, iRef);
- }
-
- /**
- * Moves the parser forward and returns the next parsing event
- *
- * @exception java.io.IOException
- * @exception ExifInvalidFormatException
- * @see #EVENT_START_OF_IFD
- * @see #EVENT_NEW_TAG
- * @see #EVENT_VALUE_OF_REGISTERED_TAG
- * @see #EVENT_COMPRESSED_IMAGE
- * @see #EVENT_UNCOMPRESSED_STRIP
- * @see #EVENT_END
- */
- protected int next() throws IOException, ExifInvalidFormatException {
- if (!mContainExifData) {
- return EVENT_END;
- }
- int offset = mTiffStream.getReadByteCount();
- int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
- if (offset < endOfTags) {
- mTag = readTag();
- if (mTag == null) {
- return next();
- }
- if (mNeedToParseOffsetsInCurrentIfd) {
- checkOffsetOrImageTag(mTag);
- }
- return EVENT_NEW_TAG;
- } else if (offset == endOfTags) {
- // There is a link to ifd1 at the end of ifd0
- if (mIfdType == IfdId.TYPE_IFD_0) {
- long ifdOffset = readUnsignedLong();
- if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
- if (ifdOffset != 0) {
- registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
- }
- }
- } else {
- int offsetSize = 4;
- // Some camera models use invalid length of the offset
- if (mCorrespondingEvent.size() > 0) {
- offsetSize = mCorrespondingEvent.firstEntry().getKey() -
- mTiffStream.getReadByteCount();
- }
- if (offsetSize < 4) {
- Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
- } else {
- long ifdOffset = readUnsignedLong();
- if (ifdOffset != 0) {
- Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
- }
- }
- }
- }
- while (mCorrespondingEvent.size() != 0) {
- Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
- Object event = entry.getValue();
- try {
- skipTo(entry.getKey());
- } catch (IOException e) {
- Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
- " for " + event.getClass().getName() + ", the file may be broken.");
- continue;
- }
- if (event instanceof IfdEvent) {
- mIfdType = ((IfdEvent) event).ifd;
- mNumOfTagInIfd = mTiffStream.readUnsignedShort();
- mIfdStartOffset = entry.getKey();
-
- if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
- Log.w(TAG, "Invalid size of IFD " + mIfdType);
- return EVENT_END;
- }
-
- mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
- if (((IfdEvent) event).isRequested) {
- return EVENT_START_OF_IFD;
- } else {
- skipRemainingTagsInCurrentIfd();
- }
- } else if (event instanceof ImageEvent) {
- mImageEvent = (ImageEvent) event;
- return mImageEvent.type;
- } else {
- ExifTagEvent tagEvent = (ExifTagEvent) event;
- mTag = tagEvent.tag;
- if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
- readFullTagValue(mTag);
- checkOffsetOrImageTag(mTag);
- }
- if (tagEvent.isRequested) {
- return EVENT_VALUE_OF_REGISTERED_TAG;
- }
- }
- }
- return EVENT_END;
- }
-
- /**
- * Skips the tags area of current IFD, if the parser is not in the tag area,
- * nothing will happen.
- *
- * @throws java.io.IOException
- * @throws ExifInvalidFormatException
- */
- protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
- int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
- int offset = mTiffStream.getReadByteCount();
- if (offset > endOfTags) {
- return;
- }
- if (mNeedToParseOffsetsInCurrentIfd) {
- while (offset < endOfTags) {
- mTag = readTag();
- offset += TAG_SIZE;
- if (mTag == null) {
- continue;
- }
- checkOffsetOrImageTag(mTag);
- }
- } else {
- skipTo(endOfTags);
- }
- long ifdOffset = readUnsignedLong();
- // For ifd0, there is a link to ifd1 in the end of all tags
- if (mIfdType == IfdId.TYPE_IFD_0
- && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
- if (ifdOffset > 0) {
- registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
- }
- }
- }
-
- private boolean needToParseOffsetsInCurrentIfd() {
- switch (mIfdType) {
- case IfdId.TYPE_IFD_0:
- return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
- || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)
- || isIfdRequested(IfdId.TYPE_IFD_1);
- case IfdId.TYPE_IFD_1:
- return isThumbnailRequested();
- case IfdId.TYPE_IFD_EXIF:
- // The offset to interoperability IFD is located in Exif IFD
- return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
- default:
- return false;
- }
- }
-
- /**
- * If {@link #next()} return {@link #EVENT_NEW_TAG} or
- * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
- * corresponding tag.
- * <p>
- * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
- * of the value is greater than 4 bytes. One should call
- * {@link ExifTag#hasValue()} to check if the tag contains value. If there
- * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
- * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
- * pointed by the offset.
- * <p>
- * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
- * tag will have already been read except for tags of undefined type. For
- * tags of undefined type, call one of the read methods to get the value.
- *
- * @see #registerForTagValue(ExifTag)
- * @see #read(byte[])
- * @see #read(byte[], int, int)
- * @see #readLong()
- * @see #readRational()
- * @see #readString(int)
- * @see #readString(int, java.nio.charset.Charset)
- */
- protected ExifTag getTag() {
- return mTag;
- }
-
- /**
- * Gets number of tags in the current IFD area.
- */
- protected int getTagCountInCurrentIfd() {
- return mNumOfTagInIfd;
- }
-
- /**
- * Gets the ID of current IFD.
- *
- * @see IfdId#TYPE_IFD_0
- * @see IfdId#TYPE_IFD_1
- * @see IfdId#TYPE_IFD_GPS
- * @see IfdId#TYPE_IFD_INTEROPERABILITY
- * @see IfdId#TYPE_IFD_EXIF
- */
- protected int getCurrentIfd() {
- return mIfdType;
- }
-
- /**
- * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
- * get the index of this strip.
- *
- * @see #getStripCount()
- */
- protected int getStripIndex() {
- return mImageEvent.stripIndex;
- }
-
- /**
- * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
- * get the number of strip data.
- *
- * @see #getStripIndex()
- */
- protected int getStripCount() {
- return mStripCount;
- }
-
- /**
- * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
- * get the strip size.
- */
- protected int getStripSize() {
- if (mStripSizeTag == null) {
- return 0;
- }
- return (int) mStripSizeTag.getValueAt(0);
- }
-
- /**
- * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
- * the image data size.
- */
- protected int getCompressedImageSize() {
- if (mJpegSizeTag == null) {
- return 0;
- }
- return (int) mJpegSizeTag.getValueAt(0);
- }
-
- private void skipTo(int offset) throws IOException {
- mTiffStream.skipTo(offset);
- while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
- mCorrespondingEvent.pollFirstEntry();
- }
- }
-
- /**
- * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
- * not contain the value if the size of the value is greater than 4 bytes.
- * When the value is not available here, call this method so that the parser
- * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
- * where the value is located.
- *
- * @see #EVENT_VALUE_OF_REGISTERED_TAG
- */
- protected void registerForTagValue(ExifTag tag) {
- if (tag.getOffset() >= mTiffStream.getReadByteCount()) {
- mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
- }
- }
-
- private void registerIfd(int ifdType, long offset) {
- // Cast unsigned int to int since the offset is always smaller
- // than the size of APP1 (65536)
- mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
- }
-
- private void registerCompressedImage(long offset) {
- mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
- }
-
- private void registerUncompressedStrip(int stripIndex, long offset) {
- mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
- , stripIndex));
- }
-
- private ExifTag readTag() throws IOException, ExifInvalidFormatException {
- short tagId = mTiffStream.readShort();
- short dataFormat = mTiffStream.readShort();
- long numOfComp = mTiffStream.readUnsignedInt();
- if (numOfComp > Integer.MAX_VALUE) {
- throw new ExifInvalidFormatException(
- "Number of component is larger then Integer.MAX_VALUE");
- }
- // Some invalid image file contains invalid data type. Ignore those tags
- if (!ExifTag.isValidType(dataFormat)) {
- Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
- mTiffStream.skip(4);
- return null;
- }
- // TODO: handle numOfComp overflow
- ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
- ((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
- int dataSize = tag.getDataSize();
- if (dataSize > 4) {
- long offset = mTiffStream.readUnsignedInt();
- if (offset > Integer.MAX_VALUE) {
- throw new ExifInvalidFormatException(
- "offset is larger then Integer.MAX_VALUE");
- }
- // Some invalid images put some undefined data before IFD0.
- // Read the data here.
- if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
- byte[] buf = new byte[(int) numOfComp];
- System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
- buf, 0, (int) numOfComp);
- tag.setValue(buf);
- } else {
- tag.setOffset((int) offset);
- }
- } else {
- boolean defCount = tag.hasDefinedCount();
- // Set defined count to 0 so we can add \0 to non-terminated strings
- tag.setHasDefinedCount(false);
- // Read value
- readFullTagValue(tag);
- tag.setHasDefinedCount(defCount);
- mTiffStream.skip(4 - dataSize);
- // Set the offset to the position of value.
- tag.setOffset(mTiffStream.getReadByteCount() - 4);
- }
- return tag;
- }
-
- /**
- * Check the tag, if the tag is one of the offset tag that points to the IFD
- * or image the caller is interested in, register the IFD or image.
- */
- private void checkOffsetOrImageTag(ExifTag tag) {
- // Some invalid formattd image contains tag with 0 size.
- if (tag.getComponentCount() == 0) {
- return;
- }
- short tid = tag.getTagId();
- int ifd = tag.getIfd();
- if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
- if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
- || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
- registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
- }
- } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
- if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
- registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
- }
- } else if (tid == TAG_INTEROPERABILITY_IFD
- && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
- if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
- registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
- }
- } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
- && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
- if (isThumbnailRequested()) {
- registerCompressedImage(tag.getValueAt(0));
- }
- } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
- && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
- if (isThumbnailRequested()) {
- mJpegSizeTag = tag;
- }
- } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
- if (isThumbnailRequested()) {
- if (tag.hasValue()) {
- for (int i = 0; i < tag.getComponentCount(); i++) {
- if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
- registerUncompressedStrip(i, tag.getValueAt(i));
- } else {
- registerUncompressedStrip(i, tag.getValueAt(i));
- }
- }
- } else {
- mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
- }
- }
- } else if (tid == TAG_STRIP_BYTE_COUNTS
- && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
- && isThumbnailRequested() && tag.hasValue()) {
- mStripSizeTag = tag;
- }
- }
-
- private boolean checkAllowed(int ifd, int tagId) {
- int info = mInterface.getTagInfo().get(tagId);
- if (info == ExifInterface.DEFINITION_NULL) {
- return false;
- }
- return ExifInterface.isIfdAllowed(info, ifd);
- }
-
- protected void readFullTagValue(ExifTag tag) throws IOException {
- // Some invalid images contains tags with wrong size, check it here
- short type = tag.getDataType();
- if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
- type == ExifTag.TYPE_UNSIGNED_BYTE) {
- int size = tag.getComponentCount();
- if (mCorrespondingEvent.size() > 0) {
- if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
- + size) {
- Object event = mCorrespondingEvent.firstEntry().getValue();
- if (event instanceof ImageEvent) {
- // Tag value overlaps thumbnail, ignore thumbnail.
- Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString());
- Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
- Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
- } else {
- // Tag value overlaps another tag, shorten count
- if (event instanceof IfdEvent) {
- Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd
- + " overlaps value for tag: \n" + tag.toString());
- } else if (event instanceof ExifTagEvent) {
- Log.w(TAG, "Tag value for tag: \n"
- + ((ExifTagEvent) event).tag.toString()
- + " overlaps value for tag: \n" + tag.toString());
- }
- size = mCorrespondingEvent.firstEntry().getKey()
- - mTiffStream.getReadByteCount();
- Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
- + " setting count to: " + size);
- tag.forceSetComponentCount(size);
- }
- }
- }
- }
- switch (tag.getDataType()) {
- case ExifTag.TYPE_UNSIGNED_BYTE:
- case ExifTag.TYPE_UNDEFINED: {
- byte buf[] = new byte[tag.getComponentCount()];
- read(buf);
- tag.setValue(buf);
- }
- break;
- case ExifTag.TYPE_ASCII:
- tag.setValue(readString(tag.getComponentCount()));
- break;
- case ExifTag.TYPE_UNSIGNED_LONG: {
- long value[] = new long[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readUnsignedLong();
- }
- tag.setValue(value);
- }
- break;
- case ExifTag.TYPE_UNSIGNED_RATIONAL: {
- Rational value[] = new Rational[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readUnsignedRational();
- }
- tag.setValue(value);
- }
- break;
- case ExifTag.TYPE_UNSIGNED_SHORT: {
- int value[] = new int[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readUnsignedShort();
- }
- tag.setValue(value);
- }
- break;
- case ExifTag.TYPE_LONG: {
- int value[] = new int[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readLong();
- }
- tag.setValue(value);
- }
- break;
- case ExifTag.TYPE_RATIONAL: {
- Rational value[] = new Rational[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readRational();
- }
- tag.setValue(value);
- }
- break;
- }
- if (LOGV) {
- Log.v(TAG, "\n" + tag.toString());
- }
- }
-
- private void parseTiffHeader() throws IOException,
- ExifInvalidFormatException {
- short byteOrder = mTiffStream.readShort();
- if (LITTLE_ENDIAN_TAG == byteOrder) {
- mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
- } else if (BIG_ENDIAN_TAG == byteOrder) {
- mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
- } else {
- throw new ExifInvalidFormatException("Invalid TIFF header");
- }
-
- if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
- throw new ExifInvalidFormatException("Invalid TIFF header");
- }
- }
-
- private boolean seekTiffData(InputStream inputStream) throws IOException,
- ExifInvalidFormatException {
- CountedDataInputStream dataStream = new CountedDataInputStream(inputStream);
- if (dataStream.readShort() != JpegHeader.SOI) {
- throw new ExifInvalidFormatException("Invalid JPEG format");
- }
-
- short marker = dataStream.readShort();
- while (marker != JpegHeader.EOI
- && !JpegHeader.isSofMarker(marker)) {
- int length = dataStream.readUnsignedShort();
- // Some invalid formatted image contains multiple APP1,
- // try to find the one with Exif data.
- if (marker == JpegHeader.APP1) {
- int header = 0;
- short headerTail = 0;
- if (length >= 8) {
- header = dataStream.readInt();
- headerTail = dataStream.readShort();
- length -= 6;
- if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
- mTiffStartPosition = dataStream.getReadByteCount();
- mApp1End = length;
- mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
- return true;
- }
- }
- }
- if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
- Log.w(TAG, "Invalid JPEG format.");
- return false;
- }
- marker = dataStream.readShort();
- }
- return false;
- }
-
- protected int getOffsetToExifEndFromSOF() {
- return mOffsetToApp1EndFromSOF;
- }
-
- protected int getTiffStartPosition() {
- return mTiffStartPosition;
- }
-
- /**
- * Reads bytes from the InputStream.
- */
- protected int read(byte[] buffer, int offset, int length) throws IOException {
- return mTiffStream.read(buffer, offset, length);
- }
-
- /**
- * Equivalent to read(buffer, 0, buffer.length).
- */
- protected int read(byte[] buffer) throws IOException {
- return mTiffStream.read(buffer);
- }
-
- /**
- * Reads a String from the InputStream with US-ASCII charset. The parser
- * will read n bytes and convert it to ascii string. This is used for
- * reading values of type {@link ExifTag#TYPE_ASCII}.
- */
- protected String readString(int n) throws IOException {
- return readString(n, US_ASCII);
- }
-
- /**
- * Reads a String from the InputStream with the given charset. The parser
- * will read n bytes and convert it to string. This is used for reading
- * values of type {@link ExifTag#TYPE_ASCII}.
- */
- protected String readString(int n, Charset charset) throws IOException {
- if (n > 0) {
- return mTiffStream.readString(n, charset);
- } else {
- return "";
- }
- }
-
- /**
- * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
- * InputStream.
- */
- protected int readUnsignedShort() throws IOException {
- return mTiffStream.readShort() & 0xffff;
- }
-
- /**
- * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
- * InputStream.
- */
- protected long readUnsignedLong() throws IOException {
- return readLong() & 0xffffffffL;
- }
-
- /**
- * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
- * InputStream.
- */
- protected Rational readUnsignedRational() throws IOException {
- long nomi = readUnsignedLong();
- long denomi = readUnsignedLong();
- return new Rational(nomi, denomi);
- }
-
- /**
- * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
- */
- protected int readLong() throws IOException {
- return mTiffStream.readInt();
- }
-
- /**
- * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
- */
- protected Rational readRational() throws IOException {
- int nomi = readLong();
- int denomi = readLong();
- return new Rational(nomi, denomi);
- }
-
- private static class ImageEvent {
- int stripIndex;
- int type;
-
- ImageEvent(int type) {
- this.stripIndex = 0;
- this.type = type;
- }
-
- ImageEvent(int type, int stripIndex) {
- this.type = type;
- this.stripIndex = stripIndex;
- }
- }
-
- private static class IfdEvent {
- int ifd;
- boolean isRequested;
-
- IfdEvent(int ifd, boolean isInterestedIfd) {
- this.ifd = ifd;
- this.isRequested = isInterestedIfd;
- }
- }
-
- private static class ExifTagEvent {
- ExifTag tag;
- boolean isRequested;
-
- ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
- this.tag = tag;
- this.isRequested = isRequireByUser;
- }
- }
-
- /**
- * Gets the byte order of the current InputStream.
- */
- protected ByteOrder getByteOrder() {
- return mTiffStream.getByteOrder();
- }
-}
diff --git a/src/com/android/messaging/util/exif/ExifReader.java b/src/com/android/messaging/util/exif/ExifReader.java
deleted file mode 100644
index eece48a..0000000
--- a/src/com/android/messaging/util/exif/ExifReader.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import android.util.Log;
-import com.android.messaging.util.LogUtil;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * This class reads the EXIF header of a JPEG file and stores it in
- * {@link ExifData}.
- */
-class ExifReader {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private final ExifInterface mInterface;
-
- ExifReader(ExifInterface iRef) {
- mInterface = iRef;
- }
-
- /**
- * Parses the inputStream and and returns the EXIF data in an
- * {@link ExifData}.
- *
- * @throws ExifInvalidFormatException
- * @throws java.io.IOException
- */
- protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
- IOException {
- ExifParser parser = ExifParser.parse(inputStream, mInterface);
- ExifData exifData = new ExifData(parser.getByteOrder());
- ExifTag tag = null;
-
- int event = parser.next();
- while (event != ExifParser.EVENT_END) {
- switch (event) {
- case ExifParser.EVENT_START_OF_IFD:
- exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
- break;
- case ExifParser.EVENT_NEW_TAG:
- tag = parser.getTag();
- if (!tag.hasValue()) {
- parser.registerForTagValue(tag);
- } else {
- exifData.getIfdData(tag.getIfd()).setTag(tag);
- }
- break;
- case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
- tag = parser.getTag();
- if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
- parser.readFullTagValue(tag);
- }
- exifData.getIfdData(tag.getIfd()).setTag(tag);
- break;
- case ExifParser.EVENT_COMPRESSED_IMAGE:
- byte buf[] = new byte[parser.getCompressedImageSize()];
- if (buf.length == parser.read(buf)) {
- exifData.setCompressedThumbnail(buf);
- } else {
- Log.w(TAG, "Failed to read the compressed thumbnail");
- }
- break;
- case ExifParser.EVENT_UNCOMPRESSED_STRIP:
- buf = new byte[parser.getStripSize()];
- if (buf.length == parser.read(buf)) {
- exifData.setStripBytes(parser.getStripIndex(), buf);
- } else {
- Log.w(TAG, "Failed to read the strip bytes");
- }
- break;
- }
- event = parser.next();
- }
- return exifData;
- }
-}
diff --git a/src/com/android/messaging/util/exif/ExifTag.java b/src/com/android/messaging/util/exif/ExifTag.java
deleted file mode 100644
index da6f4ed..0000000
--- a/src/com/android/messaging/util/exif/ExifTag.java
+++ /dev/null
@@ -1,1008 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import java.nio.charset.Charset;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-
-/**
- * This class stores information of an EXIF tag. For more information about
- * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
- * instantiated using {@link ExifInterface#buildTag}.
- *
- * @see ExifInterface
- */
-public class ExifTag {
- /**
- * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
- */
- public static final short TYPE_UNSIGNED_BYTE = 1;
- /**
- * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
- * ASCII code. The final byte is terminated with NULL.
- */
- public static final short TYPE_ASCII = 2;
- /**
- * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
- */
- public static final short TYPE_UNSIGNED_SHORT = 3;
- /**
- * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
- */
- public static final short TYPE_UNSIGNED_LONG = 4;
- /**
- * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
- * one is the numerator and the second one expresses the denominator.
- */
- public static final short TYPE_UNSIGNED_RATIONAL = 5;
- /**
- * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
- * value depending on the field definition.
- */
- public static final short TYPE_UNDEFINED = 7;
- /**
- * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
- * (2's complement notation).
- */
- public static final short TYPE_LONG = 9;
- /**
- * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
- * one is the numerator and the second one is the denominator.
- */
- public static final short TYPE_RATIONAL = 10;
-
- private static Charset US_ASCII = Charset.forName("US-ASCII");
- private static final int TYPE_TO_SIZE_MAP[] = new int[11];
- private static final int UNSIGNED_SHORT_MAX = 65535;
- private static final long UNSIGNED_LONG_MAX = 4294967295L;
- private static final long LONG_MAX = Integer.MAX_VALUE;
- private static final long LONG_MIN = Integer.MIN_VALUE;
-
- static {
- TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
- TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
- TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
- TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4;
- TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
- TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
- TYPE_TO_SIZE_MAP[TYPE_LONG] = 4;
- TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
- }
-
- static final int SIZE_UNDEFINED = 0;
-
- // Exif TagId
- private final short mTagId;
- // Exif Tag Type
- private final short mDataType;
- // If tag has defined count
- private boolean mHasDefinedDefaultComponentCount;
- // Actual data count in tag (should be number of elements in value array)
- private int mComponentCountActual;
- // The ifd that this tag should be put in
- private int mIfd;
- // The value (array of elements of type Tag Type)
- private Object mValue;
- // Value offset in exif header.
- private int mOffset;
-
- private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
-
- /**
- * Returns true if the given IFD is a valid IFD.
- */
- public static boolean isValidIfd(int ifdId) {
- return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
- || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
- || ifdId == IfdId.TYPE_IFD_GPS;
- }
-
- /**
- * Returns true if a given type is a valid tag type.
- */
- public static boolean isValidType(short type) {
- return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
- type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
- type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
- type == TYPE_LONG || type == TYPE_RATIONAL;
- }
-
- // Use builtTag in ExifInterface instead of constructor.
- ExifTag(short tagId, short type, int componentCount, int ifd,
- boolean hasDefinedComponentCount) {
- mTagId = tagId;
- mDataType = type;
- mComponentCountActual = componentCount;
- mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
- mIfd = ifd;
- mValue = null;
- }
-
- /**
- * Gets the element size of the given data type in bytes.
- *
- * @see #TYPE_ASCII
- * @see #TYPE_LONG
- * @see #TYPE_RATIONAL
- * @see #TYPE_UNDEFINED
- * @see #TYPE_UNSIGNED_BYTE
- * @see #TYPE_UNSIGNED_LONG
- * @see #TYPE_UNSIGNED_RATIONAL
- * @see #TYPE_UNSIGNED_SHORT
- */
- public static int getElementSize(short type) {
- return TYPE_TO_SIZE_MAP[type];
- }
-
- /**
- * Returns the ID of the IFD this tag belongs to.
- *
- * @see IfdId#TYPE_IFD_0
- * @see IfdId#TYPE_IFD_1
- * @see IfdId#TYPE_IFD_EXIF
- * @see IfdId#TYPE_IFD_GPS
- * @see IfdId#TYPE_IFD_INTEROPERABILITY
- */
- public int getIfd() {
- return mIfd;
- }
-
- protected void setIfd(int ifdId) {
- mIfd = ifdId;
- }
-
- /**
- * Gets the TID of this tag.
- */
- public short getTagId() {
- return mTagId;
- }
-
- /**
- * Gets the data type of this tag
- *
- * @see #TYPE_ASCII
- * @see #TYPE_LONG
- * @see #TYPE_RATIONAL
- * @see #TYPE_UNDEFINED
- * @see #TYPE_UNSIGNED_BYTE
- * @see #TYPE_UNSIGNED_LONG
- * @see #TYPE_UNSIGNED_RATIONAL
- * @see #TYPE_UNSIGNED_SHORT
- */
- public short getDataType() {
- return mDataType;
- }
-
- /**
- * Gets the total data size in bytes of the value of this tag.
- */
- public int getDataSize() {
- return getComponentCount() * getElementSize(getDataType());
- }
-
- /**
- * Gets the component count of this tag.
- */
-
- // TODO: fix integer overflows with this
- public int getComponentCount() {
- return mComponentCountActual;
- }
-
- /**
- * Sets the component count of this tag. Call this function before
- * setValue() if the length of value does not match the component count.
- */
- protected void forceSetComponentCount(int count) {
- mComponentCountActual = count;
- }
-
- /**
- * Returns true if this ExifTag contains value; otherwise, this tag will
- * contain an offset value that is determined when the tag is written.
- */
- public boolean hasValue() {
- return mValue != null;
- }
-
- /**
- * Sets integer values into this tag. This method should be used for tags of
- * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
- * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
- * <li>The value overflows.</li>
- * <li>The value.length does NOT match the component count in the definition
- * for this tag.</li>
- * </ul>
- */
- public boolean setValue(int[] value) {
- if (checkBadComponentCount(value.length)) {
- return false;
- }
- if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
- mDataType != TYPE_UNSIGNED_LONG) {
- return false;
- }
- if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
- return false;
- } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
- return false;
- }
-
- long[] data = new long[value.length];
- for (int i = 0; i < value.length; i++) {
- data[i] = value[i];
- }
- mValue = data;
- mComponentCountActual = value.length;
- return true;
- }
-
- /**
- * Sets integer value into this tag. This method should be used for tags of
- * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
- * will fail if:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
- * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
- * <li>The value overflows.</li>
- * <li>The component count in the definition of this tag is not 1.</li>
- * </ul>
- */
- public boolean setValue(int value) {
- return setValue(new int[] {
- value
- });
- }
-
- /**
- * Sets long values into this tag. This method should be used for tags of
- * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
- * <li>The value overflows.</li>
- * <li>The value.length does NOT match the component count in the definition
- * for this tag.</li>
- * </ul>
- */
- public boolean setValue(long[] value) {
- if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
- return false;
- }
- if (checkOverflowForUnsignedLong(value)) {
- return false;
- }
- mValue = value;
- mComponentCountActual = value.length;
- return true;
- }
-
- /**
- * Sets long values into this tag. This method should be used for tags of
- * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
- * <li>The value overflows.</li>
- * <li>The component count in the definition for this tag is not 1.</li>
- * </ul>
- */
- public boolean setValue(long value) {
- return setValue(new long[] {
- value
- });
- }
-
- /**
- * Sets a string value into this tag. This method should be used for tags of
- * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
- * Characters that cannot be converted are replaced with '?'. The length of
- * the string must be equal to either (component count -1) or (component
- * count). The final byte will be set to the string null terminator '\0',
- * overwriting the last character in the string if the value.length is equal
- * to the component count. This method will fail if:
- * <ul>
- * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
- * <li>The length of the string is not equal to (component count -1) or
- * (component count) in the definition for this tag.</li>
- * </ul>
- */
- public boolean setValue(String value) {
- if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
- return false;
- }
-
- byte[] buf = value.getBytes(US_ASCII);
- byte[] finalBuf = buf;
- if (buf.length > 0) {
- finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
- .copyOf(buf, buf.length + 1);
- } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) {
- finalBuf = new byte[] { 0 };
- }
- int count = finalBuf.length;
- if (checkBadComponentCount(count)) {
- return false;
- }
- mComponentCountActual = count;
- mValue = finalBuf;
- return true;
- }
-
- /**
- * Sets Rational values into this tag. This method should be used for tags
- * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
- * method will fail if:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
- * or {@link #TYPE_RATIONAL}.</li>
- * <li>The value overflows.</li>
- * <li>The value.length does NOT match the component count in the definition
- * for this tag.</li>
- * </ul>
- *
- * @see Rational
- */
- public boolean setValue(Rational[] value) {
- if (checkBadComponentCount(value.length)) {
- return false;
- }
- if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
- return false;
- }
- if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
- return false;
- } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
- return false;
- }
-
- mValue = value;
- mComponentCountActual = value.length;
- return true;
- }
-
- /**
- * Sets a Rational value into this tag. This method should be used for tags
- * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
- * method will fail if:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
- * or {@link #TYPE_RATIONAL}.</li>
- * <li>The value overflows.</li>
- * <li>The component count in the definition for this tag is not 1.</li>
- * </ul>
- *
- * @see Rational
- */
- public boolean setValue(Rational value) {
- return setValue(new Rational[] {
- value
- });
- }
-
- /**
- * Sets byte values into this tag. This method should be used for tags of
- * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
- * will fail if:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
- * {@link #TYPE_UNDEFINED} .</li>
- * <li>The length does NOT match the component count in the definition for
- * this tag.</li>
- * </ul>
- */
- public boolean setValue(byte[] value, int offset, int length) {
- if (checkBadComponentCount(length)) {
- return false;
- }
- if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
- return false;
- }
- mValue = new byte[length];
- System.arraycopy(value, offset, mValue, 0, length);
- mComponentCountActual = length;
- return true;
- }
-
- /**
- * Equivalent to setValue(value, 0, value.length).
- */
- public boolean setValue(byte[] value) {
- return setValue(value, 0, value.length);
- }
-
- /**
- * Sets byte value into this tag. This method should be used for tags of
- * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
- * will fail if:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
- * {@link #TYPE_UNDEFINED} .</li>
- * <li>The component count in the definition for this tag is not 1.</li>
- * </ul>
- */
- public boolean setValue(byte value) {
- return setValue(new byte[] {
- value
- });
- }
-
- /**
- * Sets the value for this tag using an appropriate setValue method for the
- * given object. This method will fail if:
- * <ul>
- * <li>The corresponding setValue method for the class of the object passed
- * in would fail.</li>
- * <li>There is no obvious way to cast the object passed in into an EXIF tag
- * type.</li>
- * </ul>
- */
- public boolean setValue(Object obj) {
- if (obj == null) {
- return false;
- } else if (obj instanceof Short) {
- return setValue(((Short) obj).shortValue() & 0x0ffff);
- } else if (obj instanceof String) {
- return setValue((String) obj);
- } else if (obj instanceof int[]) {
- return setValue((int[]) obj);
- } else if (obj instanceof long[]) {
- return setValue((long[]) obj);
- } else if (obj instanceof Rational) {
- return setValue((Rational) obj);
- } else if (obj instanceof Rational[]) {
- return setValue((Rational[]) obj);
- } else if (obj instanceof byte[]) {
- return setValue((byte[]) obj);
- } else if (obj instanceof Integer) {
- return setValue(((Integer) obj).intValue());
- } else if (obj instanceof Long) {
- return setValue(((Long) obj).longValue());
- } else if (obj instanceof Byte) {
- return setValue(((Byte) obj).byteValue());
- } else if (obj instanceof Short[]) {
- // Nulls in this array are treated as zeroes.
- Short[] arr = (Short[]) obj;
- int[] fin = new int[arr.length];
- for (int i = 0; i < arr.length; i++) {
- fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
- }
- return setValue(fin);
- } else if (obj instanceof Integer[]) {
- // Nulls in this array are treated as zeroes.
- Integer[] arr = (Integer[]) obj;
- int[] fin = new int[arr.length];
- for (int i = 0; i < arr.length; i++) {
- fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
- }
- return setValue(fin);
- } else if (obj instanceof Long[]) {
- // Nulls in this array are treated as zeroes.
- Long[] arr = (Long[]) obj;
- long[] fin = new long[arr.length];
- for (int i = 0; i < arr.length; i++) {
- fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
- }
- return setValue(fin);
- } else if (obj instanceof Byte[]) {
- // Nulls in this array are treated as zeroes.
- Byte[] arr = (Byte[]) obj;
- byte[] fin = new byte[arr.length];
- for (int i = 0; i < arr.length; i++) {
- fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
- }
- return setValue(fin);
- } else {
- return false;
- }
- }
-
- /**
- * Sets a timestamp to this tag. The method converts the timestamp with the
- * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
- * method will fail if the data type is not {@link #TYPE_ASCII} or the
- * component count of this tag is not 20 or undefined.
- *
- * @param time the number of milliseconds since Jan. 1, 1970 GMT
- * @return true on success
- */
- public boolean setTimeValue(long time) {
- // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
- synchronized (TIME_FORMAT) {
- return setValue(TIME_FORMAT.format(new Date(time)));
- }
- }
-
- /**
- * Gets the value as a String. This method should be used for tags of type
- * {@link #TYPE_ASCII}.
- *
- * @return the value as a String, or null if the tag's value does not exist
- * or cannot be converted to a String.
- */
- public String getValueAsString() {
- if (mValue == null) {
- return null;
- } else if (mValue instanceof String) {
- return (String) mValue;
- } else if (mValue instanceof byte[]) {
- return new String((byte[]) mValue, US_ASCII);
- }
- return null;
- }
-
- /**
- * Gets the value as a String. This method should be used for tags of type
- * {@link #TYPE_ASCII}.
- *
- * @param defaultValue the String to return if the tag's value does not
- * exist or cannot be converted to a String.
- * @return the tag's value as a String, or the defaultValue.
- */
- public String getValueAsString(String defaultValue) {
- String s = getValueAsString();
- if (s == null) {
- return defaultValue;
- }
- return s;
- }
-
- /**
- * Gets the value as a byte array. This method should be used for tags of
- * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
- *
- * @return the value as a byte array, or null if the tag's value does not
- * exist or cannot be converted to a byte array.
- */
- public byte[] getValueAsBytes() {
- if (mValue instanceof byte[]) {
- return (byte[]) mValue;
- }
- return null;
- }
-
- /**
- * Gets the value as a byte. If there are more than 1 bytes in this value,
- * gets the first byte. This method should be used for tags of type
- * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
- *
- * @param defaultValue the byte to return if tag's value does not exist or
- * cannot be converted to a byte.
- * @return the tag's value as a byte, or the defaultValue.
- */
- public byte getValueAsByte(byte defaultValue) {
- byte[] b = getValueAsBytes();
- if (b == null || b.length < 1) {
- return defaultValue;
- }
- return b[0];
- }
-
- /**
- * Gets the value as an array of Rationals. This method should be used for
- * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
- *
- * @return the value as as an array of Rationals, or null if the tag's value
- * does not exist or cannot be converted to an array of Rationals.
- */
- public Rational[] getValueAsRationals() {
- if (mValue instanceof Rational[]) {
- return (Rational[]) mValue;
- }
- return null;
- }
-
- /**
- * Gets the value as a Rational. If there are more than 1 Rationals in this
- * value, gets the first one. This method should be used for tags of type
- * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
- *
- * @param defaultValue the Rational to return if tag's value does not exist
- * or cannot be converted to a Rational.
- * @return the tag's value as a Rational, or the defaultValue.
- */
- public Rational getValueAsRational(Rational defaultValue) {
- Rational[] r = getValueAsRationals();
- if (r == null || r.length < 1) {
- return defaultValue;
- }
- return r[0];
- }
-
- /**
- * Gets the value as a Rational. If there are more than 1 Rationals in this
- * value, gets the first one. This method should be used for tags of type
- * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
- *
- * @param defaultValue the numerator of the Rational to return if tag's
- * value does not exist or cannot be converted to a Rational (the
- * denominator will be 1).
- * @return the tag's value as a Rational, or the defaultValue.
- */
- public Rational getValueAsRational(long defaultValue) {
- Rational defaultVal = new Rational(defaultValue, 1);
- return getValueAsRational(defaultVal);
- }
-
- /**
- * Gets the value as an array of ints. This method should be used for tags
- * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
- *
- * @return the value as as an array of ints, or null if the tag's value does
- * not exist or cannot be converted to an array of ints.
- */
- public int[] getValueAsInts() {
- if (mValue == null) {
- return null;
- } else if (mValue instanceof long[]) {
- long[] val = (long[]) mValue;
- int[] arr = new int[val.length];
- for (int i = 0; i < val.length; i++) {
- arr[i] = (int) val[i]; // Truncates
- }
- return arr;
- }
- return null;
- }
-
- /**
- * Gets the value as an int. If there are more than 1 ints in this value,
- * gets the first one. This method should be used for tags of type
- * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
- *
- * @param defaultValue the int to return if tag's value does not exist or
- * cannot be converted to an int.
- * @return the tag's value as a int, or the defaultValue.
- */
- public int getValueAsInt(int defaultValue) {
- int[] i = getValueAsInts();
- if (i == null || i.length < 1) {
- return defaultValue;
- }
- return i[0];
- }
-
- /**
- * Gets the value as an array of longs. This method should be used for tags
- * of type {@link #TYPE_UNSIGNED_LONG}.
- *
- * @return the value as as an array of longs, or null if the tag's value
- * does not exist or cannot be converted to an array of longs.
- */
- public long[] getValueAsLongs() {
- if (mValue instanceof long[]) {
- return (long[]) mValue;
- }
- return null;
- }
-
- /**
- * Gets the value or null if none exists. If there are more than 1 longs in
- * this value, gets the first one. This method should be used for tags of
- * type {@link #TYPE_UNSIGNED_LONG}.
- *
- * @param defaultValue the long to return if tag's value does not exist or
- * cannot be converted to a long.
- * @return the tag's value as a long, or the defaultValue.
- */
- public long getValueAsLong(long defaultValue) {
- long[] l = getValueAsLongs();
- if (l == null || l.length < 1) {
- return defaultValue;
- }
- return l[0];
- }
-
- /**
- * Gets the tag's value or null if none exists.
- */
- public Object getValue() {
- return mValue;
- }
-
- /**
- * Gets a long representation of the value.
- *
- * @param defaultValue value to return if there is no value or value is a
- * rational with a denominator of 0.
- * @return the tag's value as a long, or defaultValue if no representation
- * exists.
- */
- public long forceGetValueAsLong(long defaultValue) {
- long[] l = getValueAsLongs();
- if (l != null && l.length >= 1) {
- return l[0];
- }
- byte[] b = getValueAsBytes();
- if (b != null && b.length >= 1) {
- return b[0];
- }
- Rational[] r = getValueAsRationals();
- if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
- return (long) r[0].toDouble();
- }
- return defaultValue;
- }
-
- /**
- * Gets a string representation of the value.
- */
- public String forceGetValueAsString() {
- if (mValue == null) {
- return "";
- } else if (mValue instanceof byte[]) {
- if (mDataType == TYPE_ASCII) {
- return new String((byte[]) mValue, US_ASCII);
- } else {
- return Arrays.toString((byte[]) mValue);
- }
- } else if (mValue instanceof long[]) {
- if (((long[]) mValue).length == 1) {
- return String.valueOf(((long[]) mValue)[0]);
- } else {
- return Arrays.toString((long[]) mValue);
- }
- } else if (mValue instanceof Object[]) {
- if (((Object[]) mValue).length == 1) {
- Object val = ((Object[]) mValue)[0];
- if (val == null) {
- return "";
- } else {
- return val.toString();
- }
- } else {
- return Arrays.toString((Object[]) mValue);
- }
- } else {
- return mValue.toString();
- }
- }
-
- /**
- * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
- * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
- * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
- * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
- * {@link #getRational(int)} instead.
- *
- * @exception IllegalArgumentException if the data type is
- * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
- */
- protected long getValueAt(int index) {
- if (mValue instanceof long[]) {
- return ((long[]) mValue)[index];
- } else if (mValue instanceof byte[]) {
- return ((byte[]) mValue)[index];
- }
- throw new IllegalArgumentException("Cannot get integer value from "
- + convertTypeToString(mDataType));
- }
-
- /**
- * Gets the {@link #TYPE_ASCII} data.
- *
- * @exception IllegalArgumentException If the type is NOT
- * {@link #TYPE_ASCII}.
- */
- protected String getString() {
- if (mDataType != TYPE_ASCII) {
- throw new IllegalArgumentException("Cannot get ASCII value from "
- + convertTypeToString(mDataType));
- }
- return new String((byte[]) mValue, US_ASCII);
- }
-
- /*
- * Get the converted ascii byte. Used by ExifOutputStream.
- */
- protected byte[] getStringByte() {
- return (byte[]) mValue;
- }
-
- /**
- * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
- *
- * @exception IllegalArgumentException If the type is NOT
- * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
- */
- protected Rational getRational(int index) {
- if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
- throw new IllegalArgumentException("Cannot get RATIONAL value from "
- + convertTypeToString(mDataType));
- }
- return ((Rational[]) mValue)[index];
- }
-
- /**
- * Equivalent to getBytes(buffer, 0, buffer.length).
- */
- protected void getBytes(byte[] buf) {
- getBytes(buf, 0, buf.length);
- }
-
- /**
- * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
- *
- * @param buf the byte array in which to store the bytes read.
- * @param offset the initial position in buffer to store the bytes.
- * @param length the maximum number of bytes to store in buffer. If length >
- * component count, only the valid bytes will be stored.
- * @exception IllegalArgumentException If the type is NOT
- * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
- */
- protected void getBytes(byte[] buf, int offset, int length) {
- if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
- throw new IllegalArgumentException("Cannot get BYTE value from "
- + convertTypeToString(mDataType));
- }
- System.arraycopy(mValue, 0, buf, offset,
- (length > mComponentCountActual) ? mComponentCountActual : length);
- }
-
- /**
- * Gets the offset of this tag. This is only valid if this data size > 4 and
- * contains an offset to the location of the actual value.
- */
- protected int getOffset() {
- return mOffset;
- }
-
- /**
- * Sets the offset of this tag.
- */
- protected void setOffset(int offset) {
- mOffset = offset;
- }
-
- protected void setHasDefinedCount(boolean d) {
- mHasDefinedDefaultComponentCount = d;
- }
-
- protected boolean hasDefinedCount() {
- return mHasDefinedDefaultComponentCount;
- }
-
- private boolean checkBadComponentCount(int count) {
- if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
- return true;
- }
- return false;
- }
-
- private static String convertTypeToString(short type) {
- switch (type) {
- case TYPE_UNSIGNED_BYTE:
- return "UNSIGNED_BYTE";
- case TYPE_ASCII:
- return "ASCII";
- case TYPE_UNSIGNED_SHORT:
- return "UNSIGNED_SHORT";
- case TYPE_UNSIGNED_LONG:
- return "UNSIGNED_LONG";
- case TYPE_UNSIGNED_RATIONAL:
- return "UNSIGNED_RATIONAL";
- case TYPE_UNDEFINED:
- return "UNDEFINED";
- case TYPE_LONG:
- return "LONG";
- case TYPE_RATIONAL:
- return "RATIONAL";
- default:
- return "";
- }
- }
-
- private boolean checkOverflowForUnsignedShort(int[] value) {
- for (int v : value) {
- if (v > UNSIGNED_SHORT_MAX || v < 0) {
- return true;
- }
- }
- return false;
- }
-
- private boolean checkOverflowForUnsignedLong(long[] value) {
- for (long v : value) {
- if (v < 0 || v > UNSIGNED_LONG_MAX) {
- return true;
- }
- }
- return false;
- }
-
- private boolean checkOverflowForUnsignedLong(int[] value) {
- for (int v : value) {
- if (v < 0) {
- return true;
- }
- }
- return false;
- }
-
- private boolean checkOverflowForUnsignedRational(Rational[] value) {
- for (Rational v : value) {
- if (v.getNumerator() < 0 || v.getDenominator() < 0
- || v.getNumerator() > UNSIGNED_LONG_MAX
- || v.getDenominator() > UNSIGNED_LONG_MAX) {
- return true;
- }
- }
- return false;
- }
-
- private boolean checkOverflowForRational(Rational[] value) {
- for (Rational v : value) {
- if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
- || v.getNumerator() > LONG_MAX
- || v.getDenominator() > LONG_MAX) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- }
- if (obj instanceof ExifTag) {
- ExifTag tag = (ExifTag) obj;
- if (tag.mTagId != this.mTagId
- || tag.mComponentCountActual != this.mComponentCountActual
- || tag.mDataType != this.mDataType) {
- return false;
- }
- if (mValue != null) {
- if (tag.mValue == null) {
- return false;
- } else if (mValue instanceof long[]) {
- if (!(tag.mValue instanceof long[])) {
- return false;
- }
- return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
- } else if (mValue instanceof Rational[]) {
- if (!(tag.mValue instanceof Rational[])) {
- return false;
- }
- return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
- } else if (mValue instanceof byte[]) {
- if (!(tag.mValue instanceof byte[])) {
- return false;
- }
- return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
- } else {
- return mValue.equals(tag.mValue);
- }
- } else {
- return tag.mValue == null;
- }
- }
- return false;
- }
-
- @Override
- public String toString() {
- return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
- + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
- + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
- }
-
-}
diff --git a/src/com/android/messaging/util/exif/IfdData.java b/src/com/android/messaging/util/exif/IfdData.java
deleted file mode 100644
index 6b8c293..0000000
--- a/src/com/android/messaging/util/exif/IfdData.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * This class stores all the tags in an IFD.
- *
- * @see ExifData
- * @see ExifTag
- */
-class IfdData {
-
- private final int mIfdId;
- private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
- private int mOffsetToNextIfd = 0;
- private static final int[] sIfds = {
- IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
- IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
- };
- /**
- * Creates an IfdData with given IFD ID.
- *
- * @see IfdId#TYPE_IFD_0
- * @see IfdId#TYPE_IFD_1
- * @see IfdId#TYPE_IFD_EXIF
- * @see IfdId#TYPE_IFD_GPS
- * @see IfdId#TYPE_IFD_INTEROPERABILITY
- */
- IfdData(int ifdId) {
- mIfdId = ifdId;
- }
-
- protected static int[] getIfds() {
- return sIfds;
- }
-
- /**
- * Get a array the contains all {@link ExifTag} in this IFD.
- */
- protected ExifTag[] getAllTags() {
- return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
- }
-
- /**
- * Gets the ID of this IFD.
- *
- * @see IfdId#TYPE_IFD_0
- * @see IfdId#TYPE_IFD_1
- * @see IfdId#TYPE_IFD_EXIF
- * @see IfdId#TYPE_IFD_GPS
- * @see IfdId#TYPE_IFD_INTEROPERABILITY
- */
- protected int getId() {
- return mIfdId;
- }
-
- /**
- * Gets the {@link ExifTag} with given tag id. Return null if there is no
- * such tag.
- */
- protected ExifTag getTag(short tagId) {
- return mExifTags.get(tagId);
- }
-
- /**
- * Adds or replaces a {@link ExifTag}.
- */
- protected ExifTag setTag(ExifTag tag) {
- tag.setIfd(mIfdId);
- return mExifTags.put(tag.getTagId(), tag);
- }
-
- protected boolean checkCollision(short tagId) {
- return mExifTags.get(tagId) != null;
- }
-
- /**
- * Removes the tag of the given ID
- */
- protected void removeTag(short tagId) {
- mExifTags.remove(tagId);
- }
-
- /**
- * Gets the tags count in the IFD.
- */
- protected int getTagCount() {
- return mExifTags.size();
- }
-
- /**
- * Sets the offset of next IFD.
- */
- protected void setOffsetToNextIfd(int offset) {
- mOffsetToNextIfd = offset;
- }
-
- /**
- * Gets the offset of next IFD.
- */
- protected int getOffsetToNextIfd() {
- return mOffsetToNextIfd;
- }
-
- /**
- * Returns true if all tags in this two IFDs are equal. Note that tags of
- * IFDs offset or thumbnail offset will be ignored.
- */
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (obj instanceof IfdData) {
- IfdData data = (IfdData) obj;
- if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
- ExifTag[] tags = data.getAllTags();
- for (ExifTag tag : tags) {
- if (ExifInterface.isOffsetTag(tag.getTagId())) {
- continue;
- }
- ExifTag tag2 = mExifTags.get(tag.getTagId());
- if (!tag.equals(tag2)) {
- return false;
- }
- }
- return true;
- }
- }
- return false;
- }
-}
diff --git a/src/com/android/messaging/util/exif/IfdId.java b/src/com/android/messaging/util/exif/IfdId.java
deleted file mode 100644
index 06a820d..0000000
--- a/src/com/android/messaging/util/exif/IfdId.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-/**
- * The constants of the IFD ID defined in EXIF spec.
- */
-public interface IfdId {
- public static final int TYPE_IFD_0 = 0;
- public static final int TYPE_IFD_1 = 1;
- public static final int TYPE_IFD_EXIF = 2;
- public static final int TYPE_IFD_INTEROPERABILITY = 3;
- public static final int TYPE_IFD_GPS = 4;
- /* This is used in ExifData to allocate enough IfdData */
- static final int TYPE_IFD_COUNT = 5;
-
-}
diff --git a/src/com/android/messaging/util/exif/JpegHeader.java b/src/com/android/messaging/util/exif/JpegHeader.java
deleted file mode 100644
index 1dd12a5..0000000
--- a/src/com/android/messaging/util/exif/JpegHeader.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-class JpegHeader {
- public static final short SOI = (short) 0xFFD8;
- public static final short APP1 = (short) 0xFFE1;
- public static final short APP0 = (short) 0xFFE0;
- public static final short EOI = (short) 0xFFD9;
-
- /**
- * SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG,
- * and DAC marker.
- */
- public static final short SOF0 = (short) 0xFFC0;
- public static final short SOF15 = (short) 0xFFCF;
- public static final short DHT = (short) 0xFFC4;
- public static final short JPG = (short) 0xFFC8;
- public static final short DAC = (short) 0xFFCC;
-
- public static final boolean isSofMarker(short marker) {
- return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG
- && marker != DAC;
- }
-}
diff --git a/src/com/android/messaging/util/exif/OrderedDataOutputStream.java b/src/com/android/messaging/util/exif/OrderedDataOutputStream.java
deleted file mode 100644
index cf64805..0000000
--- a/src/com/android/messaging/util/exif/OrderedDataOutputStream.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-class OrderedDataOutputStream extends FilterOutputStream {
- private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
-
- public OrderedDataOutputStream(OutputStream out) {
- super(out);
- }
-
- public OrderedDataOutputStream setByteOrder(ByteOrder order) {
- mByteBuffer.order(order);
- return this;
- }
-
- public OrderedDataOutputStream writeShort(short value) throws IOException {
- mByteBuffer.rewind();
- mByteBuffer.putShort(value);
- out.write(mByteBuffer.array(), 0, 2);
- return this;
- }
-
- public OrderedDataOutputStream writeInt(int value) throws IOException {
- mByteBuffer.rewind();
- mByteBuffer.putInt(value);
- out.write(mByteBuffer.array());
- return this;
- }
-
- public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
- writeInt((int) rational.getNumerator());
- writeInt((int) rational.getDenominator());
- return this;
- }
-}
diff --git a/src/com/android/messaging/util/exif/Rational.java b/src/com/android/messaging/util/exif/Rational.java
deleted file mode 100644
index b42ceb7..0000000
--- a/src/com/android/messaging/util/exif/Rational.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2012 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.messaging.util.exif;
-
-/**
- * The rational data type of EXIF tag. Contains a pair of longs representing the
- * numerator and denominator of a Rational number.
- */
-public class Rational {
-
- private final long mNumerator;
- private final long mDenominator;
-
- /**
- * Create a Rational with a given numerator and denominator.
- *
- * @param nominator
- * @param denominator
- */
- public Rational(long nominator, long denominator) {
- mNumerator = nominator;
- mDenominator = denominator;
- }
-
- /**
- * Create a copy of a Rational.
- */
- public Rational(Rational r) {
- mNumerator = r.mNumerator;
- mDenominator = r.mDenominator;
- }
-
- /**
- * Gets the numerator of the rational.
- */
- public long getNumerator() {
- return mNumerator;
- }
-
- /**
- * Gets the denominator of the rational
- */
- public long getDenominator() {
- return mDenominator;
- }
-
- /**
- * Gets the rational value as type double. Will cause a divide-by-zero error
- * if the denominator is 0.
- */
- public double toDouble() {
- return mNumerator / (double) mDenominator;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- }
- if (this == obj) {
- return true;
- }
- if (obj instanceof Rational) {
- Rational data = (Rational) obj;
- return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
- }
- return false;
- }
-
- @Override
- public String toString() {
- return mNumerator + "/" + mDenominator;
- }
-}
diff --git a/src/com/android/messaging/widget/BaseWidgetFactory.java b/src/com/android/messaging/widget/BaseWidgetFactory.java
deleted file mode 100644
index 30b80ae..0000000
--- a/src/com/android/messaging/widget/BaseWidgetFactory.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.widget;
-
-import android.appwidget.AppWidgetManager;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.Binder;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.style.StyleSpan;
-import android.widget.RemoteViews;
-import android.widget.RemoteViewsService;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor;
-import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Remote Views Factory for Bugle Widget.
- */
-abstract class BaseWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
- protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
-
- protected static final int MAX_ITEMS_TO_SHOW = 25;
-
- /**
- * Lock to avoid race condition between widgets.
- */
- protected static final Object sWidgetLock = new Object();
-
- protected final Context mContext;
- protected final int mAppWidgetId;
- protected boolean mShouldShowViewMore;
- protected Cursor mCursor;
- protected final AppWidgetManager mAppWidgetManager;
- protected int mIconSize;
- protected ImageResource mAvatarResource;
-
- public BaseWidgetFactory(Context context, Intent intent) {
- mContext = context;
- mAppWidgetId = intent.getIntExtra(
- AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- mAppWidgetManager = AppWidgetManager.getInstance(context);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "BaseWidgetFactory intent: " + intent + "widget id: " + mAppWidgetId);
- }
- mIconSize = (int) context.getResources()
- .getDimension(R.dimen.contact_icon_view_normal_size);
-
- }
-
- @Override
- public void onCreate() {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onCreate");
- }
- }
-
- @Override
- public void onDestroy() {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onDestroy");
- }
- synchronized (sWidgetLock) {
- if (mCursor != null && !mCursor.isClosed()) {
- mCursor.close();
- mCursor = null;
- }
- }
- }
-
- @Override
- public void onDataSetChanged() {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onDataSetChanged");
- }
- synchronized (sWidgetLock) {
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mCursor = doQuery();
- onLoadComplete();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
-
- protected abstract Cursor doQuery();
-
- /**
- * Returns the number of items that should be shown in the widget list. This method also
- * updates the boolean that indicates whether the "show more" item should be shown.
- * @return the number of items to be displayed in the list.
- */
- @Override
- public int getCount() {
- synchronized (sWidgetLock) {
- if (mCursor == null) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getCount: 0");
- }
- return 0;
- }
- final int count = getItemCount();
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getCount: " + count);
- }
- mShouldShowViewMore = count < mCursor.getCount();
- return count + (mShouldShowViewMore ? 1 : 0);
- }
- }
-
- /**
- * Returns the number of messages that should be shown in the widget. This method
- * doesn't update the boolean that indicates whether the "show more" item should be included
- * in the list.
- * @return
- */
- protected int getItemCount() {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getItemCount: " + mCursor.getCount());
- }
- return Math.min(mCursor.getCount(), MAX_ITEMS_TO_SHOW);
- }
-
- /*
- * Make the given text bold if the item is unread
- */
- protected CharSequence boldifyIfUnread(CharSequence text, final boolean unread) {
- if (!unread) {
- return text;
- }
- final SpannableStringBuilder builder = new SpannableStringBuilder(text);
- builder.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return builder;
- }
-
- protected Bitmap getAvatarBitmap(final Uri avatarUri) {
- final String avatarType = avatarUri == null ?
- null : AvatarUriUtil.getAvatarType(avatarUri);
- ImageRequestDescriptor descriptor;
- if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) {
- descriptor = new AvatarGroupRequestDescriptor(avatarUri, mIconSize, mIconSize);
- } else {
- descriptor = new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize);
- }
-
- final MediaRequest<ImageResource> imageRequest =
- descriptor.buildSyncMediaRequest(mContext);
- final ImageResource imageResource =
- MediaResourceManager.get().requestMediaResourceSync(imageRequest);
- if (imageResource != null) {
- setAvatarResource(imageResource);
- return mAvatarResource.getBitmap();
- } else {
- releaseAvatarResource();
- return null;
- }
- }
-
- /**
- * @return the "View more messages" view. When the user taps this item, they're
- * taken to the conversation in Bugle.
- */
- abstract protected RemoteViews getViewMoreItemsView();
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- private void onLoadComplete() {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onLoadComplete");
- }
- final RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(),
- getMainLayoutId());
- mAppWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews);
- }
-
- protected abstract int getMainLayoutId();
-
- private void setAvatarResource(final ImageResource resource) {
- if (mAvatarResource != resource) {
- // Clear out any information for what is currently used
- releaseAvatarResource();
- mAvatarResource = resource;
- }
- }
-
- private void releaseAvatarResource() {
- if (mAvatarResource != null) {
- mAvatarResource.release();
- }
- mAvatarResource = null;
- }
-}
diff --git a/src/com/android/messaging/widget/BaseWidgetProvider.java b/src/com/android/messaging/widget/BaseWidgetProvider.java
deleted file mode 100644
index 431a6c7..0000000
--- a/src/com/android/messaging/widget/BaseWidgetProvider.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.widget;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.messaging.util.LogUtil;
-
-public abstract class BaseWidgetProvider extends AppWidgetProvider {
- protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
-
- public static final int WIDGET_CONVERSATION_REQUEST_CODE = 987;
-
- static final String WIDGET_SIZE_KEY = "widgetSizeKey";
-
- public static final int SIZE_LARGE = 0; // undefined == 0, which is the default, large
- public static final int SIZE_SMALL = 1;
- public static final int SIZE_MEDIUM = 2;
- public static final int SIZE_PRE_JB = 3;
-
- /**
- * Update all widgets in the list
- */
- @Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- super.onUpdate(context, appWidgetManager, appWidgetIds);
-
- for (int i = 0; i < appWidgetIds.length; ++i) {
- updateWidget(context, appWidgetIds[i]);
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onReceive intent: " + intent + " for " + this.getClass());
- }
- final String action = intent.getAction();
-
- // The base class AppWidgetProvider's onReceive handles the normal widget intents. Here
- // we're looking for an intent sent by our app when it knows a message has
- // been sent or received (or a conversation has been read) and is telling the widget it
- // needs to update.
- if (getAction().equals(action)) {
- final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
- this.getClass()));
-
- if (appWidgetIds.length > 0) {
- // We need to update all Bugle app widgets on the home screen.
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onReceive notifyAppWidgetViewDataChanged listId: " +
- getListId() + " first widgetId: " + appWidgetIds[0]);
- }
- appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, getListId());
- }
- } else {
- super.onReceive(context, intent);
- }
- }
-
- protected abstract String getAction();
-
- protected abstract int getListId();
-
- /**
- * Update the widget appWidgetId
- */
- protected abstract void updateWidget(Context context, int appWidgetId);
-
- private int getWidgetSize(AppWidgetManager appWidgetManager,
- int appWidgetId) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize");
- }
-
- // Get the dimensions
- final Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
-
- // Get min width and height.
- final int minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
- final int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
-
- // First find out rows and columns based on width provided.
- final int rows = getCellsForSize(minHeight);
- final int columns = getCellsForSize(minWidth);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize row: " + rows +
- " columns: " + columns);
- }
-
- int size = SIZE_MEDIUM;
- if (rows == 1) {
- size = SIZE_SMALL; // Our widget doesn't let itself get this small. Perhaps in the
- // future will add a super-mini widget.
- } else if (columns > 3) {
- size = SIZE_LARGE;
- }
-
- // put the size in the bundle so our service know what size it's dealing with.
- final int savedSize = options.getInt(WIDGET_SIZE_KEY);
- if (savedSize != size) {
- options.putInt(WIDGET_SIZE_KEY, size);
- appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
-
- // The size changed. We have to force the widget to rebuild the list.
- appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, getListId());
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize old size: " + savedSize +
- " new size saved: " + size);
- }
- }
-
- return size;
- }
-
- /**
- * Returns number of cells needed for given size of the widget.
- *
- * @param size Widget size in dp.
- * @return Size in number of cells.
- */
- private static int getCellsForSize(int size) {
- // The hardwired sizes in this function come from the hardwired formula found in
- // Android's UI guidelines for widget design:
- // http://developer.android.com/guide/practices/ui_guidelines/widget_design.html
- return (size + 30) / 70;
- }
-
- @Override
- public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
- int appWidgetId, Bundle newOptions) {
-
- final int widgetSize = getWidgetSize(appWidgetManager, appWidgetId);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "BaseWidgetProvider.onAppWidgetOptionsChanged new size: " +
- widgetSize);
- }
-
- super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
- }
-
- protected void deletePreferences(final int widgetId) {
- }
-
- /**
- * Remove preferences when deleting widget
- */
- @Override
- public void onDeleted(Context context, int[] appWidgetIds) {
- super.onDeleted(context, appWidgetIds);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "BaseWidgetProvider.onDeleted");
- }
-
- for (final int widgetId : appWidgetIds) {
- deletePreferences(widgetId);
- }
- }
-
-}
diff --git a/src/com/android/messaging/widget/BugleWidgetProvider.java b/src/com/android/messaging/widget/BugleWidgetProvider.java
deleted file mode 100644
index 50c97b6..0000000
--- a/src/com/android/messaging/widget/BugleWidgetProvider.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.widget;
-
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.widget.RemoteViews;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UiUtils;
-
-public class BugleWidgetProvider extends BaseWidgetProvider {
- public static final String ACTION_NOTIFY_CONVERSATIONS_CHANGED =
- "com.android.Bugle.intent.action.ACTION_NOTIFY_CONVERSATIONS_CHANGED";
-
- public static final int WIDGET_NEW_CONVERSATION_REQUEST_CODE = 986;
-
- /**
- * Update the widget appWidgetId
- */
- @Override
- protected void updateWidget(final Context context, final int appWidgetId) {
- if (OsUtil.hasRequiredPermissions()) {
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- rebuildWidget(context, appWidgetId);
- }
- });
- } else {
- AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId,
- UiUtils.getWidgetMissingPermissionView(context));
- }
- }
-
- @Override
- protected String getAction() {
- return ACTION_NOTIFY_CONVERSATIONS_CHANGED;
- }
-
- @Override
- protected int getListId() {
- return R.id.conversation_list;
- }
-
- public static void rebuildWidget(final Context context, final int appWidgetId) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "BugleWidgetProvider.rebuildWidget appWidgetId: " + appWidgetId);
- }
- final RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
- R.layout.widget_conversation_list);
- PendingIntent clickIntent;
-
- // Launch an intent to avoid ANRs
- final Intent intent = new Intent(context, WidgetConversationListService.class);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
- remoteViews.setRemoteAdapter(appWidgetId, R.id.conversation_list, intent);
-
- remoteViews.setTextViewText(R.id.widget_label, context.getString(R.string.app_name));
-
- // Open Bugle's app conversation list when click on header
- clickIntent = UIIntents.get().getWidgetPendingIntentForConversationListActivity(context);
- remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
-
- // On click intent for Compose
- clickIntent = UIIntents.get().getWidgetPendingIntentForConversationActivity(context,
- null /*conversationId*/, WIDGET_NEW_CONVERSATION_REQUEST_CODE);
- remoteViews.setOnClickPendingIntent(R.id.widget_compose, clickIntent);
-
- // On click intent for Conversation
- // Note: the template intent has to be a "naked" intent without any extras. It turns out
- // that if the template intent does have extras, those particular extras won't get
- // replaced by the fill-in intent on each list item.
- clickIntent = UIIntents.get().getWidgetPendingIntentForConversationActivity(context,
- null /*conversationId*/, WIDGET_CONVERSATION_REQUEST_CODE);
- remoteViews.setPendingIntentTemplate(R.id.conversation_list, clickIntent);
-
- AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews);
- }
-
- /*
- * notifyDatasetChanged call when the conversation list changes so the Bugle widget will
- * update and reflect the changes
- */
- public static void notifyConversationListChanged(final Context context) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "notifyConversationListChanged");
- }
- final Intent intent = new Intent(ACTION_NOTIFY_CONVERSATIONS_CHANGED);
- context.sendBroadcast(intent);
- }
-}
diff --git a/src/com/android/messaging/widget/WidgetConversationListService.java b/src/com/android/messaging/widget/WidgetConversationListService.java
deleted file mode 100644
index 264b98c..0000000
--- a/src/com/android/messaging/widget/WidgetConversationListService.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.widget;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.RemoteViewsService;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.conversationlist.ConversationListItemView;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-public class WidgetConversationListService extends RemoteViewsService {
- private static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
-
- @Override
- public RemoteViewsFactory onGetViewFactory(Intent intent) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onGetViewFactory intent: " + intent);
- }
- return new WidgetConversationListFactory(getApplicationContext(), intent);
- }
-
- /**
- * Remote Views Factory for Bugle Widget.
- */
- private static class WidgetConversationListFactory extends BaseWidgetFactory {
-
- public WidgetConversationListFactory(Context context, Intent intent) {
- super(context, intent);
- }
-
- @Override
- protected Cursor doQuery() {
- return mContext.getContentResolver().query(MessagingContentProvider.CONVERSATIONS_URI,
- ConversationListItemData.PROJECTION,
- ConversationListData.WHERE_NOT_ARCHIVED,
- null, // selection args
- ConversationListData.SORT_ORDER);
- }
-
- /**
- * @return the {@link RemoteViews} for a specific position in the list.
- */
- @Override
- public RemoteViews getViewAt(int position) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getViewAt position: " + position);
- }
- synchronized (sWidgetLock) {
- // "View more conversations" view.
- if (mCursor == null
- || (mShouldShowViewMore && position >= getItemCount())) {
- return getViewMoreItemsView();
- }
-
- if (!mCursor.moveToPosition(position)) {
- // If we ever fail to move to a position, return the "View More conversations"
- // view.
- LogUtil.w(TAG, "Failed to move to position: " + position);
- return getViewMoreItemsView();
- }
-
- final ConversationListItemData conv = new ConversationListItemData();
- conv.bind(mCursor);
-
- // Inflate and fill out the remote view
- final RemoteViews remoteViews = new RemoteViews(
- mContext.getPackageName(), R.layout.widget_conversation_list_item);
-
- final boolean hasUnreadMessages = !conv.getIsRead();
- final Resources resources = mContext.getResources();
- final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
-
- final String timeStamp = conv.getIsSendRequested() ?
- resources.getString(R.string.message_status_sending) :
- Dates.getWidgetTimeString(conv.getTimestamp(), true /*abbreviated*/)
- .toString();
- // Date/Timestamp or Sending or Error state -- all shown in the date item
- remoteViews.setTextViewText(R.id.date,
- boldifyIfUnread(timeStamp, hasUnreadMessages));
-
- // From
- remoteViews.setTextViewText(R.id.from,
- boldifyIfUnread(conv.getName(), hasUnreadMessages));
-
- // Notifications turned off mini-bell icon
- remoteViews.setViewVisibility(R.id.conversation_notification_bell,
- conv.getNotificationEnabled() ? View.GONE : View.VISIBLE);
-
- // On click intent.
- final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
- conv.getConversationId(), null /* draft */);
-
- remoteViews.setOnClickFillInIntent(R.id.widget_conversation_list_item, intent);
-
- // Avatar
- boolean includeAvatar;
- if (OsUtil.isAtLeastJB()) {
- final Bundle options = mAppWidgetManager.getAppWidgetOptions(mAppWidgetId);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getViewAt BugleWidgetProvider.WIDGET_SIZE_KEY: " +
- options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY));
- }
-
- includeAvatar = options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY) ==
- BugleWidgetProvider.SIZE_LARGE;
- } else {
- includeAvatar = true;;
- }
-
- // Show the avatar when grande size, otherwise hide it.
- remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ?
- View.VISIBLE : View.GONE);
-
- Uri iconUri = null;
- if (conv.getIcon() != null) {
- iconUri = Uri.parse(conv.getIcon());
- }
- remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ?
- getAvatarBitmap(iconUri) : null);
-
- // Error
- // Only show the fail icon if it is not a group conversation.
- // And also require that we be the default sms app.
- final boolean showError = conv.getIsFailedStatus() &&
- isDefaultSmsApp;
- final boolean showDraft = conv.getShowDraft() &&
- isDefaultSmsApp;
- remoteViews.setViewVisibility(R.id.conversation_failed_status_icon,
- showError && includeAvatar ?
- View.VISIBLE : View.GONE);
-
- if (showError || showDraft) {
- remoteViews.setViewVisibility(R.id.snippet, View.GONE);
- remoteViews.setViewVisibility(R.id.errorBlock, View.VISIBLE);
- remoteViews.setTextViewText(R.id.errorSnippet, getSnippetText(conv));
-
- if (showDraft) {
- // Show italicized "Draft" on third line
- final String text = resources.getString(
- R.string.conversation_list_item_view_draft_message);
- SpannableStringBuilder builder = new SpannableStringBuilder(text);
- builder.setSpan(new StyleSpan(Typeface.ITALIC), 0, text.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- builder.setSpan(new ForegroundColorSpan(
- resources.getColor(R.color.widget_text_color)),
- 0, text.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- remoteViews.setTextViewText(R.id.errorText, builder);
- } else {
- // Show error message on third line
- int failureMessageId = R.string.message_status_download_failed;
- if (conv.getIsMessageTypeOutgoing()) {
- failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(
- conv.getMessageStatus(),
- conv.getMessageRawTelephonyStatus());
- }
- remoteViews.setTextViewText(R.id.errorText,
- resources.getString(failureMessageId));
- }
- } else {
- remoteViews.setViewVisibility(R.id.errorBlock, View.GONE);
- remoteViews.setViewVisibility(R.id.snippet, View.VISIBLE);
- remoteViews.setTextViewText(R.id.snippet,
- boldifyIfUnread(getSnippetText(conv), hasUnreadMessages));
- }
-
- // Set the accessibility TalkBack text
- remoteViews.setContentDescription(R.id.widget_conversation_list_item,
- ConversationListItemView.buildContentDescription(mContext.getResources(),
- conv, new TextPaint()));
-
- return remoteViews;
- }
- }
-
- private String getSnippetText(final ConversationListItemData conv) {
- String snippetText = conv.getShowDraft() ?
- conv.getDraftSnippetText() : conv.getSnippetText();
- final String previewContentType = conv.getShowDraft() ?
- conv.getDraftPreviewContentType() : conv.getPreviewContentType();
- if (TextUtils.isEmpty(snippetText)) {
- Resources resources = mContext.getResources();
- // Use the attachment type as a snippet so the preview doesn't look odd
- if (ContentType.isAudioType(previewContentType)) {
- snippetText = resources.getString(
- R.string.conversation_list_snippet_audio_clip);
- } else if (ContentType.isImageType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_picture);
- } else if (ContentType.isVideoType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_video);
- } else if (ContentType.isVCardType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_vcard);
- }
- }
- return snippetText;
- }
-
- /**
- * @return the "View more conversations" view. When the user taps this item, they're
- * taken to the Bugle's conversation list.
- */
- @Override
- protected RemoteViews getViewMoreItemsView() {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getViewMoreItemsView");
- }
- final RemoteViews view = new RemoteViews(mContext.getPackageName(),
- R.layout.widget_loading);
- view.setTextViewText(
- R.id.loading_text, mContext.getText(R.string.view_more_conversations));
-
- // Tapping this "More conversations" item should take us to the ConversationList.
- // However, the list view is primed with an intent to go to the Conversation activity.
- // Each normal conversation list item sets the fill-in intent with the
- // ConversationId for that particular conversation. In other words, the only place
- // we can go is the ConversationActivity. We add an extra here to tell the
- // ConversationActivity to really take us to the ConversationListActivity.
- final Intent intent = new Intent();
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, true);
- view.setOnClickFillInIntent(R.id.widget_loading, intent);
- return view;
- }
-
- @Override
- public RemoteViews getLoadingView() {
- RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading);
- view.setTextViewText(
- R.id.loading_text, mContext.getText(R.string.loading_conversations));
- return view;
- }
-
- @Override
- public int getViewTypeCount() {
- return 2;
- }
-
- @Override
- protected int getMainLayoutId() {
- return R.layout.widget_conversation_list;
- }
- }
-
-}
diff --git a/src/com/android/messaging/widget/WidgetConversationProvider.java b/src/com/android/messaging/widget/WidgetConversationProvider.java
deleted file mode 100644
index 6ae5614..0000000
--- a/src/com/android/messaging/widget/WidgetConversationProvider.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.widget;
-
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Looper;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.RemoteViews;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.WidgetPickConversationActivity;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UiUtils;
-
-public class WidgetConversationProvider extends BaseWidgetProvider {
- public static final String ACTION_NOTIFY_MESSAGES_CHANGED =
- "com.android.Bugle.intent.action.ACTION_NOTIFY_MESSAGES_CHANGED";
-
- public static final int WIDGET_CONVERSATION_TEMPLATE_REQUEST_CODE = 1985;
- public static final int WIDGET_CONVERSATION_REPLY_CODE = 1987;
-
- // Intent extras
- public static final String UI_INTENT_EXTRA_RECIPIENT = "recipient";
- public static final String UI_INTENT_EXTRA_ICON = "icon";
-
- /**
- * Update the widget appWidgetId
- */
- @Override
- protected void updateWidget(final Context context, final int appWidgetId) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "updateWidget appWidgetId: " + appWidgetId);
- }
- if (OsUtil.hasRequiredPermissions()) {
- rebuildWidget(context, appWidgetId);
- } else {
- AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId,
- UiUtils.getWidgetMissingPermissionView(context));
- }
- }
-
- @Override
- protected String getAction() {
- return ACTION_NOTIFY_MESSAGES_CHANGED;
- }
-
- @Override
- protected int getListId() {
- return R.id.message_list;
- }
-
- public static void rebuildWidget(final Context context, final int appWidgetId) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "WidgetConversationProvider.rebuildWidget appWidgetId: " + appWidgetId);
- }
- final RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
- R.layout.widget_conversation);
- PendingIntent clickIntent;
- final UIIntents uiIntents = UIIntents.get();
- if (!isWidgetConfigured(appWidgetId)) {
- // Widget has not been configured yet. Hide the normal UI elements and show the
- // configuration view instead.
- remoteViews.setViewVisibility(R.id.widget_label, View.GONE);
- remoteViews.setViewVisibility(R.id.message_list, View.GONE);
- remoteViews.setViewVisibility(R.id.launcher_icon, View.VISIBLE);
- remoteViews.setViewVisibility(R.id.widget_configuration, View.VISIBLE);
-
- remoteViews.setOnClickPendingIntent(R.id.widget_configuration,
- uiIntents.getWidgetPendingIntentForConfigurationActivity(context, appWidgetId));
-
- // On click intent for Goto Conversation List
- clickIntent = uiIntents.getWidgetPendingIntentForConversationListActivity(context);
- remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "WidgetConversationProvider.rebuildWidget appWidgetId: " +
- appWidgetId + " going into configure state");
- }
- } else {
- remoteViews.setViewVisibility(R.id.widget_label, View.VISIBLE);
- remoteViews.setViewVisibility(R.id.message_list, View.VISIBLE);
- remoteViews.setViewVisibility(R.id.launcher_icon, View.GONE);
- remoteViews.setViewVisibility(R.id.widget_configuration, View.GONE);
-
- final String conversationId =
- WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
- final boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
- // If we're running on the UI thread, we can't do the DB access needed to get the
- // conversation data. We'll do excute this again off of the UI thread.
- final ConversationListItemData convData = isMainThread ?
- null : getConversationData(context, conversationId);
-
- // Launch an intent to avoid ANRs
- final Intent intent = new Intent(context, WidgetConversationService.class);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
- remoteViews.setRemoteAdapter(appWidgetId, R.id.message_list, intent);
-
- remoteViews.setTextViewText(R.id.widget_label, convData != null ?
- convData.getName() : context.getString(R.string.app_name));
-
- // On click intent for Goto Conversation List
- clickIntent = uiIntents.getWidgetPendingIntentForConversationListActivity(context);
- remoteViews.setOnClickPendingIntent(R.id.widget_goto_conversation_list, clickIntent);
-
- // Open the conversation when click on header
- clickIntent = uiIntents.getWidgetPendingIntentForConversationActivity(context,
- conversationId, WIDGET_CONVERSATION_REQUEST_CODE);
- remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
-
- // On click intent for Conversation
- // Note: the template intent has to be a "naked" intent without any extras. It turns out
- // that if the template intent does have extras, those particular extras won't get
- // replaced by the fill-in intent on each list item.
- clickIntent = uiIntents.getWidgetPendingIntentForConversationActivity(context,
- conversationId, WIDGET_CONVERSATION_TEMPLATE_REQUEST_CODE);
- remoteViews.setPendingIntentTemplate(R.id.message_list, clickIntent);
-
- if (isMainThread) {
- // We're running on the UI thread and we couldn't update all the parts of the
- // widget dependent on ConversationListItemData. However, we have to update
- // the widget regardless, even with those missing pieces. Here we update the
- // widget again in the background.
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- rebuildWidget(context, appWidgetId);
- }
- });
- }
- }
-
- AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews);
-
- }
-
- /*
- * notifyMessagesChanged called when the conversation changes so the widget will
- * update and reflect the changes
- */
- public static void notifyMessagesChanged(final Context context, final String conversationId) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "notifyMessagesChanged");
- }
- final Intent intent = new Intent(ACTION_NOTIFY_MESSAGES_CHANGED);
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- context.sendBroadcast(intent);
- }
-
- /*
- * notifyConversationDeleted is called when a conversation is deleted. Look through all the
- * widgets and if they're displaying that conversation, force the widget into its
- * configuration state.
- */
- public static void notifyConversationDeleted(final Context context,
- final String conversationId) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "notifyConversationDeleted convId: " + conversationId);
- }
-
- final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- for (final int appWidgetId : appWidgetManager.getAppWidgetIds(new ComponentName(context,
- WidgetConversationProvider.class))) {
- // Retrieve the persisted information for this widget from preferences.
- final String widgetConvId =
- WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
-
- if (widgetConvId == null || widgetConvId.equals(conversationId)) {
- if (widgetConvId != null) {
- WidgetPickConversationActivity.deleteConversationIdPref(appWidgetId);
- }
- rebuildWidget(context, appWidgetId);
- }
- }
- }
-
- /*
- * notifyConversationRenamed is called when a conversation is renamed. Look through all the
- * widgets and if they're displaying that conversation, force the widget to rebuild itself
- */
- public static void notifyConversationRenamed(final Context context,
- final String conversationId) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "notifyConversationRenamed convId: " + conversationId);
- }
-
- final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- for (final int appWidgetId : appWidgetManager.getAppWidgetIds(new ComponentName(context,
- WidgetConversationProvider.class))) {
- // Retrieve the persisted information for this widget from preferences.
- final String widgetConvId =
- WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
-
- if (widgetConvId != null && widgetConvId.equals(conversationId)) {
- rebuildWidget(context, appWidgetId);
- }
- }
- }
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "WidgetConversationProvider onReceive intent: " + intent);
- }
- final String action = intent.getAction();
-
- // The base class AppWidgetProvider's onReceive handles the normal widget intents. Here
- // we're looking for an intent sent by our app when it knows a message has
- // been sent or received (or a conversation has been read) and is telling the widget it
- // needs to update.
- if (getAction().equals(action)) {
- final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
- this.getClass()));
-
- if (appWidgetIds.length == 0) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "WidgetConversationProvider onReceive no widget ids");
- }
- return;
- }
- // Normally the conversation id points to a specific conversation and we only update
- // widgets looking at that conversation. When the conversation id is null, that means
- // there's been a massive change (such as the initial import) and we need to update
- // every conversation widget.
- final String conversationId = intent.getExtras()
- .getString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
-
- // Only update the widgets that match the conversation id that changed.
- for (final int widgetId : appWidgetIds) {
- // Retrieve the persisted information for this widget from preferences.
- final String widgetConvId =
- WidgetPickConversationActivity.getConversationIdPref(widgetId);
- if (conversationId == null || TextUtils.equals(conversationId, widgetConvId)) {
- // Update the list portion (i.e. the message list) of the widget
- appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, getListId());
- }
- }
- } else {
- super.onReceive(context, intent);
- }
- }
-
- private static ConversationListItemData getConversationData(final Context context,
- final String conversationId) {
- if (TextUtils.isEmpty(conversationId)) {
- return null;
- }
- final Uri uri = MessagingContentProvider.buildConversationMetadataUri(conversationId);
- Cursor cursor = null;
- try {
- cursor = context.getContentResolver().query(uri,
- ConversationListItemData.PROJECTION,
- null, // selection
- null, // selection args
- null); // sort order
- if (cursor != null && cursor.getCount() > 0) {
- final ConversationListItemData conv = new ConversationListItemData();
- cursor.moveToFirst();
- conv.bind(cursor);
- return conv;
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return null;
- }
-
- @Override
- protected void deletePreferences(final int widgetId) {
- WidgetPickConversationActivity.deleteConversationIdPref(widgetId);
- }
-
- /**
- * When this widget is created, it's created for a particular conversation and that
- * ConversationId is stored in shared prefs. If the associated conversation is deleted,
- * the widget doesn't get deleted. Instead, it goes into a "tap to configure" state. This
- * function determines whether the widget has been configured and has an associated
- * ConversationId.
- */
- public static boolean isWidgetConfigured(final int appWidgetId) {
- final String conversationId =
- WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
- return !TextUtils.isEmpty(conversationId);
- }
-
-}
diff --git a/src/com/android/messaging/widget/WidgetConversationService.java b/src/com/android/messaging/widget/WidgetConversationService.java
deleted file mode 100644
index 4fd3934..0000000
--- a/src/com/android/messaging/widget/WidgetConversationService.java
+++ /dev/null
@@ -1,521 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.widget;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Formatter;
-import android.text.style.ForegroundColorSpan;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.RemoteViewsService;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.MessagePartImageRequestDescriptor;
-import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.datamodel.media.VideoThumbnailRequest;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.List;
-
-public class WidgetConversationService extends RemoteViewsService {
- private static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
-
- private static final int IMAGE_ATTACHMENT_SIZE = 400;
-
- @Override
- public RemoteViewsFactory onGetViewFactory(Intent intent) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onGetViewFactory intent: " + intent);
- }
- return new WidgetConversationFactory(getApplicationContext(), intent);
- }
-
- /**
- * Remote Views Factory for the conversation widget.
- */
- private static class WidgetConversationFactory extends BaseWidgetFactory {
- private ImageResource mImageResource;
- private String mConversationId;
-
- public WidgetConversationFactory(Context context, Intent intent) {
- super(context, intent);
-
- mConversationId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "BugleFactory intent: " + intent + "widget id: " + mAppWidgetId);
- }
- mIconSize = (int) context.getResources()
- .getDimension(R.dimen.contact_icon_view_normal_size);
- }
-
- @Override
- public void onCreate() {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "onCreate");
- }
- super.onCreate();
-
- // If the conversation for this widget has been removed, we want to update the widget to
- // "Tap to configure" mode.
- if (!WidgetConversationProvider.isWidgetConfigured(mAppWidgetId)) {
- WidgetConversationProvider.rebuildWidget(mContext, mAppWidgetId);
- }
- }
-
- @Override
- protected Cursor doQuery() {
- if (TextUtils.isEmpty(mConversationId)) {
- LogUtil.w(TAG, "doQuery no conversation id");
- return null;
- }
- final Uri uri = MessagingContentProvider.buildConversationMessagesUri(mConversationId);
- if (uri != null) {
- LogUtil.w(TAG, "doQuery uri: " + uri.toString());
- }
- return mContext.getContentResolver().query(uri,
- ConversationMessageData.getProjection(),
- null, // where
- null, // selection args
- null // sort order
- );
- }
-
- /**
- * @return the {@link RemoteViews} for a specific position in the list.
- */
- @Override
- public RemoteViews getViewAt(final int originalPosition) {
- synchronized (sWidgetLock) {
- // "View more messages" view.
- if (mCursor == null
- || (mShouldShowViewMore && originalPosition == 0)) {
- return getViewMoreItemsView();
- }
- // The message cursor is in reverse order for performance reasons.
- final int position = getCount() - originalPosition - 1;
- if (!mCursor.moveToPosition(position)) {
- // If we ever fail to move to a position, return the "View More messages"
- // view.
- LogUtil.w(TAG, "Failed to move to position: " + position);
- return getViewMoreItemsView();
- }
-
- final ConversationMessageData message = new ConversationMessageData();
- message.bind(mCursor);
-
- // Inflate and fill out the remote view
- final RemoteViews remoteViews = new RemoteViews(
- mContext.getPackageName(), message.getIsIncoming() ?
- R.layout.widget_message_item_incoming :
- R.layout.widget_message_item_outgoing);
-
- final boolean hasUnreadMessages = false; //!message.getIsRead();
-
- // Date
- remoteViews.setTextViewText(R.id.date, boldifyIfUnread(
- Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
- false /*abbreviated*/),
- hasUnreadMessages));
-
- // On click intent.
- final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
- mConversationId, null /* draft */);
-
- // Attachments
- int attachmentStringId = 0;
- remoteViews.setViewVisibility(R.id.attachmentFrame, View.GONE);
-
- int scrollToPosition = originalPosition;
- final int cursorCount = mCursor.getCount();
- if (cursorCount > MAX_ITEMS_TO_SHOW) {
- scrollToPosition += cursorCount - MAX_ITEMS_TO_SHOW;
- }
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getViewAt position: " + originalPosition +
- " computed position: " + position +
- " scrollToPosition: " + scrollToPosition +
- " cursorCount: " + cursorCount +
- " MAX_ITEMS_TO_SHOW: " + MAX_ITEMS_TO_SHOW);
- }
-
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, scrollToPosition);
- if (message.hasAttachments()) {
- final List<MessagePartData> attachments = message.getAttachments();
- for (MessagePartData part : attachments) {
- final boolean videoWithThumbnail = part.isVideo()
- && (VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()
- || !message.getIsIncoming());
- if (part.isImage() || videoWithThumbnail) {
- final Uri uri = part.getContentUri();
- remoteViews.setViewVisibility(R.id.attachmentFrame, View.VISIBLE);
- remoteViews.setViewVisibility(R.id.playButton, part.isVideo() ?
- View.VISIBLE : View.GONE);
- remoteViews.setImageViewBitmap(R.id.attachment,
- getAttachmentBitmap(part));
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI ,
- uri.toString());
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE ,
- part.getContentType());
- break;
- } else if (part.isVideo()) {
- attachmentStringId = R.string.conversation_list_snippet_video;
- break;
- }
- if (part.isAudio()) {
- attachmentStringId = R.string.conversation_list_snippet_audio_clip;
- break;
- }
- if (part.isVCard()) {
- attachmentStringId = R.string.conversation_list_snippet_vcard;
- break;
- }
- }
- }
-
- remoteViews.setOnClickFillInIntent(message.getIsIncoming() ?
- R.id.widget_message_item_incoming :
- R.id.widget_message_item_outgoing,
- intent);
-
- // Avatar
- boolean includeAvatar;
- if (OsUtil.isAtLeastJB()) {
- final Bundle options = mAppWidgetManager.getAppWidgetOptions(mAppWidgetId);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getViewAt BugleWidgetProvider.WIDGET_SIZE_KEY: " +
- options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY));
- }
-
- includeAvatar = options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY)
- == BugleWidgetProvider.SIZE_LARGE;
- } else {
- includeAvatar = true;
- }
-
- // Show the avatar (and shadow) when grande size, otherwise hide it.
- remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ?
- View.VISIBLE : View.GONE);
- remoteViews.setViewVisibility(R.id.avatarShadow, includeAvatar ?
- View.VISIBLE : View.GONE);
-
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- message.getSenderProfilePhotoUri(),
- message.getSenderFullName(),
- message.getSenderNormalizedDestination(),
- message.getSenderContactLookupKey());
-
- remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ?
- getAvatarBitmap(avatarUri) : null);
-
- String text = message.getText();
- if (attachmentStringId != 0) {
- final String attachment = mContext.getString(attachmentStringId);
- if (!TextUtils.isEmpty(text)) {
- text += '\n' + attachment;
- } else {
- text = attachment;
- }
- }
-
- remoteViews.setViewVisibility(R.id.message, View.VISIBLE);
- updateViewContent(text, message, remoteViews);
-
- return remoteViews;
- }
- }
-
- // updateViewContent figures out what to show in the message and date fields based on
- // the message status. This code came from ConversationMessageView.updateViewContent, but
- // had to be simplified to work with our simple widget list item.
- // updateViewContent also builds the accessibility content description for the list item.
- private void updateViewContent(final String messageText,
- final ConversationMessageData message,
- final RemoteViews remoteViews) {
- int titleResId = -1;
- int statusResId = -1;
- boolean showInRed = false;
- String statusText = null;
- switch(message.getStatus()) {
- case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
- titleResId = R.string.message_title_downloading;
- statusResId = R.string.message_status_downloading;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_manual_download;
- statusResId = R.string.message_status_download;
- }
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_download_failed;
- statusResId = R.string.message_status_download_error;
- showInRed = true;
- }
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_download_failed;
- statusResId = R.string.message_status_download;
- showInRed = true;
- }
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
- case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
- statusResId = R.string.message_status_sending;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
- case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
- statusResId = R.string.message_status_send_retrying;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
- statusResId = R.string.message_status_send_failed_emergency_number;
- showInRed = true;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
- // don't show the error state unless we're the default sms app
- if (PhoneUtils.getDefault().isDefaultSmsApp()) {
- statusResId = MmsUtils.mapRawStatusToErrorResourceId(
- message.getStatus(), message.getRawTelephonyStatus());
- showInRed = true;
- break;
- }
- // FALL THROUGH HERE
-
- case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
- case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
- default:
- if (!message.getCanClusterWithNextMessage()) {
- statusText = Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
- false /*abbreviated*/).toString();
- }
- break;
- }
-
- // Build the content description while we're populating the various fields.
- final StringBuilder description = new StringBuilder();
- final String separator = mContext.getString(R.string.enumeration_comma);
- // Sender information
- final boolean hasPlainTextMessage = !(TextUtils.isEmpty(message.getText()));
- if (message.getIsIncoming()) {
- int senderResId = hasPlainTextMessage
- ? R.string.incoming_text_sender_content_description
- : R.string.incoming_sender_content_description;
- description.append(mContext.getString(senderResId, message.getSenderDisplayName()));
- } else {
- int senderResId = hasPlainTextMessage
- ? R.string.outgoing_text_sender_content_description
- : R.string.outgoing_sender_content_description;
- description.append(mContext.getString(senderResId));
- }
-
- final boolean titleVisible = (titleResId >= 0);
- if (titleVisible) {
- final String titleText = mContext.getString(titleResId);
- remoteViews.setTextViewText(R.id.message, titleText);
-
- final String mmsInfoText = mContext.getString(
- R.string.mms_info,
- Formatter.formatFileSize(mContext, message.getSmsMessageSize()),
- DateUtils.formatDateTime(
- mContext,
- message.getMmsExpiry(),
- DateUtils.FORMAT_SHOW_DATE |
- DateUtils.FORMAT_SHOW_TIME |
- DateUtils.FORMAT_NUMERIC_DATE |
- DateUtils.FORMAT_NO_YEAR));
- remoteViews.setTextViewText(R.id.date, mmsInfoText);
- description.append(separator);
- description.append(mmsInfoText);
- } else if (!TextUtils.isEmpty(messageText)) {
- remoteViews.setTextViewText(R.id.message, messageText);
- description.append(separator);
- description.append(messageText);
- } else {
- remoteViews.setViewVisibility(R.id.message, View.GONE);
- }
-
- final String subjectText = MmsUtils.cleanseMmsSubject(mContext.getResources(),
- message.getMmsSubject());
- if (!TextUtils.isEmpty(subjectText)) {
- description.append(separator);
- description.append(subjectText);
- }
-
- if (statusResId >= 0) {
- statusText = mContext.getString(statusResId);
- final Spannable colorStr = new SpannableString(statusText);
- if (showInRed) {
- colorStr.setSpan(new ForegroundColorSpan(
- mContext.getResources().getColor(R.color.timestamp_text_failed)),
- 0, statusText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- remoteViews.setTextViewText(R.id.date, colorStr);
- description.append(separator);
- description.append(colorStr);
- } else {
- description.append(separator);
- description.append(Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
- false /*abbreviated*/));
- }
-
- if (message.hasAttachments()) {
- final List<MessagePartData> attachments = message.getAttachments();
- int stringId;
- for (MessagePartData part : attachments) {
- if (part.isImage()) {
- stringId = R.string.conversation_list_snippet_picture;
- } else if (part.isVideo()) {
- stringId = R.string.conversation_list_snippet_video;
- } else if (part.isAudio()) {
- stringId = R.string.conversation_list_snippet_audio_clip;
- } else if (part.isVCard()) {
- stringId = R.string.conversation_list_snippet_vcard;
- } else {
- stringId = 0;
- }
- if (stringId > 0) {
- description.append(separator);
- description.append(mContext.getString(stringId));
- }
- }
- }
- remoteViews.setContentDescription(message.getIsIncoming() ?
- R.id.widget_message_item_incoming :
- R.id.widget_message_item_outgoing, description);
- }
-
- private Bitmap getAttachmentBitmap(final MessagePartData part) {
- UriImageRequestDescriptor descriptor;
- if (part.isImage()) {
- descriptor = new MessagePartImageRequestDescriptor(part,
- IMAGE_ATTACHMENT_SIZE, // desiredWidth
- IMAGE_ATTACHMENT_SIZE, // desiredHeight
- true // isStatic
- );
- } else if (part.isVideo()) {
- descriptor = new MessagePartVideoThumbnailRequestDescriptor(part);
- } else {
- return null;
- }
-
- final MediaRequest<ImageResource> imageRequest =
- descriptor.buildSyncMediaRequest(mContext);
- final ImageResource imageResource =
- MediaResourceManager.get().requestMediaResourceSync(imageRequest);
- if (imageResource != null && imageResource.getBitmap() != null) {
- setImageResource(imageResource);
- return Bitmap.createBitmap(imageResource.getBitmap());
- } else {
- releaseImageResource();
- return null;
- }
- }
-
- /**
- * @return the "View more messages" view. When the user taps this item, they're
- * taken to the conversation in Bugle.
- */
- @Override
- protected RemoteViews getViewMoreItemsView() {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "getViewMoreConversationsView");
- }
- final RemoteViews view = new RemoteViews(mContext.getPackageName(),
- R.layout.widget_loading);
- view.setTextViewText(
- R.id.loading_text, mContext.getText(R.string.view_more_messages));
-
- // Tapping this "More messages" item should take us to the conversation.
- final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
- mConversationId, null /* draft */);
- view.setOnClickFillInIntent(R.id.widget_loading, intent);
- return view;
- }
-
- @Override
- public RemoteViews getLoadingView() {
- final RemoteViews view = new RemoteViews(mContext.getPackageName(),
- R.layout.widget_loading);
- view.setTextViewText(
- R.id.loading_text, mContext.getText(R.string.loading_messages));
- return view;
- }
-
- @Override
- public int getViewTypeCount() {
- return 3; // Number of different list items that can be returned -
- // 1- incoming list item
- // 2- outgoing list item
- // 3- more items list item
- }
-
- @Override
- protected int getMainLayoutId() {
- return R.layout.widget_conversation;
- }
-
- private void setImageResource(final ImageResource resource) {
- if (mImageResource != resource) {
- // Clear out any information for what is currently used
- releaseImageResource();
- mImageResource = resource;
- }
- }
-
- private void releaseImageResource() {
- if (mImageResource != null) {
- mImageResource.release();
- }
- mImageResource = null;
- }
- }
-
-}