Support for disappearing messages
// FREEBIE
109
build.gradle
@ -72,10 +72,14 @@ dependencies {
|
||||
compile 'org.whispersystems:jobmanager:1.0.2'
|
||||
compile 'org.whispersystems:libpastelog:1.0.7'
|
||||
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
compile 'org.whispersystems:signal-service-android:2.1.1'
|
||||
compile 'org.whispersystems:signal-service-android:2.2.0'
|
||||
compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0'
|
||||
compile 'com.google.zxing:core:3.2.1'
|
||||
|
||||
compile ('cn.carbswang.android:NumberPickerView:1.0.9') {
|
||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||
}
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.assertj:assertj-core:1.7.1'
|
||||
testCompile 'org.mockito:mockito-core:1.9.5'
|
||||
@ -97,57 +101,58 @@ dependencies {
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'me.leolin:ShortcutBadger:3142d017234bfa0cdd69ccded7cc5ea63f13b97574803c8c616c9bbeaad33ad9',
|
||||
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
|
||||
'com.google.android.gms:play-services-gcm:757ecd2c837ac81c98f4cc7dc783e7454c6d0506f6cc66b10417126b675248c9',
|
||||
'com.google.android.gms:play-services-maps:c58a9d98a98889fb0b27f78100f2d9341ed7722db24ccf832df62b6e8ce1b42e',
|
||||
'com.google.android.gms:play-services-location:8226f778aa86bd15b9143f62425262cc53d64021990f62eb1aaec108d4e25f35',
|
||||
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
|
||||
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
|
||||
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
|
||||
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
|
||||
'com.github.bumptech.glide:glide:76ef123957b5fbaebb05fcbe6606dd58c3bc3fcdadb257f99811d0ac9ea9b88b',
|
||||
'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1',
|
||||
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
|
||||
'de.greenrobot:eventbus:61d743a748156a372024d083de763b9e91ac2dcb3f6a1cbc74995c7ddab6e968',
|
||||
'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
|
||||
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
||||
'com.android.support:appcompat-v7:4b5ccba8c4557ef04f99aa0a80f8aa7d50f05f926a709010a54afd5c878d3618',
|
||||
'com.android.support:recyclerview-v7:b0f530a5b14334d56ce0de85527ffe93ac419bc928e2884287ce1dddfedfb505',
|
||||
'com.android.support:design:58be3ca6a73789615f7ece0937d2f683b98b594bb90aa10565fa760fb10b07ee',
|
||||
'com.android.support:cardview-v7:2c2354761a4e20ba451ae903ab808f15c9acc8343b1e74001869c2d0a672c1fc',
|
||||
'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
|
||||
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||
'com.android.support:gridlayout-v7:a9b770cffca2c7c5cd83cba4dd12503365de5e8d9c79c479165adf18ab3bc25b',
|
||||
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
|
||||
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
|
||||
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
|
||||
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
|
||||
'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88',
|
||||
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
|
||||
'org.whispersystems:signal-service-android:1c89623336505f6511e6f68ea126c85eae7f28f6c72beb6b362e5743bc5e5126',
|
||||
'com.h6ah4i.android.compat:mulsellistprefcompat:47167c5cb796de1a854788e9ff318358e36c8fb88123baaa6e38fb78511dfabe',
|
||||
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
||||
'com.google.android.gms:play-services-base:ef36e50fa5c0415ed41f74dd399a889efd2fa327c449036e140c7c3786aa0e1f',
|
||||
'com.android.support:support-annotations:104f353b53d5dd8d64b2f77eece4b37f6b961de9732eb6b706395e91033ec70a',
|
||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
||||
'org.whispersystems:signal-service-java:48db52056aa3510deb8c4ccd2dfb35033ae115bc4176048820c6dff73290ba6e',
|
||||
'org.whispersystems:signal-protocol-android:d83cb3d15b667fc2543fa18ce80791c72c053e8ac54fc2941f0429a5944ca691',
|
||||
'com.google.android.gms:play-services-basement:e1d29b21e02fd2a63e5a31807415cbb17a59568e27e3254181c01ffae10659bf',
|
||||
'com.googlecode.libphonenumber:libphonenumber:9625de9d2270e9a280ff4e6d9ef3106573fb4828773fd32c9b7614f4e17d2811',
|
||||
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
||||
'com.squareup.okhttp:okhttp:89b7f63e2e5b6c410266abc14f50fe52ea8d2d8a57260829e499b1cd9f0e61af',
|
||||
'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d',
|
||||
'org.whispersystems:curve25519-android:d6a3ef3a70622af4c728b7fe5f8fdfc9e6cd39b1d39b2c77e7a2add9d876bc23',
|
||||
'org.whispersystems:signal-protocol-java:d518d52eeb3c44210e0b6c687360848a87afbaee0bdf42e2a8dd9974d54fdb3a',
|
||||
'com.squareup.okio:okio:5e1098bd3fdee4c3347f5ab815b40ba851e4ab1b348c5e49a5b0362f0ce6e978',
|
||||
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
|
||||
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
|
||||
'org.whispersystems:curve25519-java:08cc3be52723e0fc4148e5e7002d51d6d7e495b2130022237f2d47b90af6ae0b',
|
||||
'com.android.support:support-v4:c62f0d025dafa86f423f48df9185b0d89496adbc5f6a9be5a7c394d84cf91423',
|
||||
'me.leolin:ShortcutBadger:3142d017234bfa0cdd69ccded7cc5ea63f13b97574803c8c616c9bbeaad33ad9',
|
||||
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
|
||||
'com.google.android.gms:play-services-gcm:757ecd2c837ac81c98f4cc7dc783e7454c6d0506f6cc66b10417126b675248c9',
|
||||
'com.google.android.gms:play-services-maps:c58a9d98a98889fb0b27f78100f2d9341ed7722db24ccf832df62b6e8ce1b42e',
|
||||
'com.google.android.gms:play-services-location:8226f778aa86bd15b9143f62425262cc53d64021990f62eb1aaec108d4e25f35',
|
||||
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
|
||||
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
|
||||
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
|
||||
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
|
||||
'com.github.bumptech.glide:glide:76ef123957b5fbaebb05fcbe6606dd58c3bc3fcdadb257f99811d0ac9ea9b88b',
|
||||
'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1',
|
||||
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
|
||||
'de.greenrobot:eventbus:61d743a748156a372024d083de763b9e91ac2dcb3f6a1cbc74995c7ddab6e968',
|
||||
'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
|
||||
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
||||
'com.android.support:appcompat-v7:4b5ccba8c4557ef04f99aa0a80f8aa7d50f05f926a709010a54afd5c878d3618',
|
||||
'com.android.support:recyclerview-v7:b0f530a5b14334d56ce0de85527ffe93ac419bc928e2884287ce1dddfedfb505',
|
||||
'com.android.support:design:58be3ca6a73789615f7ece0937d2f683b98b594bb90aa10565fa760fb10b07ee',
|
||||
'com.android.support:cardview-v7:2c2354761a4e20ba451ae903ab808f15c9acc8343b1e74001869c2d0a672c1fc',
|
||||
'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
|
||||
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||
'com.android.support:gridlayout-v7:a9b770cffca2c7c5cd83cba4dd12503365de5e8d9c79c479165adf18ab3bc25b',
|
||||
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
|
||||
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
|
||||
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
|
||||
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
|
||||
'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88',
|
||||
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
|
||||
'org.whispersystems:signal-service-android:96a926b0bfd1df8b66be2b574e8b8d6ef1862f715b0f1a5457a2038b28d3ad1b',
|
||||
'com.h6ah4i.android.compat:mulsellistprefcompat:47167c5cb796de1a854788e9ff318358e36c8fb88123baaa6e38fb78511dfabe',
|
||||
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
||||
'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729',
|
||||
'com.google.android.gms:play-services-base:ef36e50fa5c0415ed41f74dd399a889efd2fa327c449036e140c7c3786aa0e1f',
|
||||
'com.android.support:support-annotations:104f353b53d5dd8d64b2f77eece4b37f6b961de9732eb6b706395e91033ec70a',
|
||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
||||
'org.whispersystems:signal-protocol-android:d83cb3d15b667fc2543fa18ce80791c72c053e8ac54fc2941f0429a5944ca691',
|
||||
'org.whispersystems:signal-service-java:7932363fec666fdc0b4b424eeca4bdca235f6bf2f226fb6a6ff742c49fc37087',
|
||||
'com.google.android.gms:play-services-basement:e1d29b21e02fd2a63e5a31807415cbb17a59568e27e3254181c01ffae10659bf',
|
||||
'org.whispersystems:curve25519-android:d6a3ef3a70622af4c728b7fe5f8fdfc9e6cd39b1d39b2c77e7a2add9d876bc23',
|
||||
'org.whispersystems:signal-protocol-java:d518d52eeb3c44210e0b6c687360848a87afbaee0bdf42e2a8dd9974d54fdb3a',
|
||||
'com.googlecode.libphonenumber:libphonenumber:9625de9d2270e9a280ff4e6d9ef3106573fb4828773fd32c9b7614f4e17d2811',
|
||||
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
||||
'com.squareup.okhttp:okhttp:89b7f63e2e5b6c410266abc14f50fe52ea8d2d8a57260829e499b1cd9f0e61af',
|
||||
'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d',
|
||||
'org.whispersystems:curve25519-java:08cc3be52723e0fc4148e5e7002d51d6d7e495b2130022237f2d47b90af6ae0b',
|
||||
'com.squareup.okio:okio:5e1098bd3fdee4c3347f5ab815b40ba851e4ab1b348c5e49a5b0362f0ce6e978',
|
||||
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
|
||||
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
|
||||
'com.android.support:support-v4:c62f0d025dafa86f423f48df9185b0d89496adbc5f6a9be5a7c394d84cf91423',
|
||||
]
|
||||
}
|
||||
|
||||
|
BIN
res/drawable-hdpi/ic_hourglass_empty_white_18dp.png
Normal file
After Width: | Height: | Size: 298 B |
BIN
res/drawable-hdpi/ic_hourglass_full_white_18dp.png
Normal file
After Width: | Height: | Size: 230 B |
BIN
res/drawable-hdpi/ic_timer_off_white_24dp.png
Normal file
After Width: | Height: | Size: 555 B |
BIN
res/drawable-hdpi/ic_timer_white_24dp.png
Normal file
After Width: | Height: | Size: 499 B |
BIN
res/drawable-mdpi/ic_hourglass_empty_white_18dp.png
Normal file
After Width: | Height: | Size: 224 B |
BIN
res/drawable-mdpi/ic_hourglass_full_white_18dp.png
Normal file
After Width: | Height: | Size: 195 B |
BIN
res/drawable-mdpi/ic_timer_off_white_24dp.png
Normal file
After Width: | Height: | Size: 361 B |
BIN
res/drawable-mdpi/ic_timer_white_24dp.png
Normal file
After Width: | Height: | Size: 329 B |
BIN
res/drawable-xhdpi/ic_hourglass_empty_white_18dp.png
Normal file
After Width: | Height: | Size: 273 B |
BIN
res/drawable-xhdpi/ic_hourglass_full_white_18dp.png
Normal file
After Width: | Height: | Size: 197 B |
BIN
res/drawable-xhdpi/ic_timer_off_white_24dp.png
Normal file
After Width: | Height: | Size: 641 B |
BIN
res/drawable-xhdpi/ic_timer_white_24dp.png
Normal file
After Width: | Height: | Size: 628 B |
BIN
res/drawable-xxhdpi/ic_hourglass_empty_white_18dp.png
Normal file
After Width: | Height: | Size: 509 B |
BIN
res/drawable-xxhdpi/ic_hourglass_full_white_18dp.png
Normal file
After Width: | Height: | Size: 349 B |
BIN
res/drawable-xxhdpi/ic_timer_off_white_24dp.png
Normal file
After Width: | Height: | Size: 986 B |
BIN
res/drawable-xxhdpi/ic_timer_white_24dp.png
Normal file
After Width: | Height: | Size: 901 B |
BIN
res/drawable-xxxhdpi/ic_hourglass_empty_white_18dp.png
Normal file
After Width: | Height: | Size: 417 B |
BIN
res/drawable-xxxhdpi/ic_hourglass_full_white_18dp.png
Normal file
After Width: | Height: | Size: 279 B |
BIN
res/drawable-xxxhdpi/ic_timer_off_white_24dp.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-xxxhdpi/ic_timer_white_24dp.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
@ -46,7 +46,8 @@
|
||||
android:layout_toRightOf="@id/contact_photo"
|
||||
android:layout_marginRight="35dp"
|
||||
android:background="@drawable/received_bubble"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
tools:backgroundTint="@color/blue_900">
|
||||
|
||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
||||
android:id="@+id/image_view"
|
||||
@ -115,14 +116,28 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingRight="4dp"
|
||||
android:paddingRight="2dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:src="?menu_lock_icon_small"
|
||||
android:contentDescription="@string/conversation_item__secure_message_description"
|
||||
android:visibility="gone"
|
||||
android:tint="?conversation_item_received_text_secondary_color"
|
||||
android:tintMode="multiply"/>
|
||||
android:tintMode="multiply"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.ExpirationTimerView
|
||||
android:id="@+id/expiration_indicator"
|
||||
app:empty="@drawable/ic_hourglass_empty_white_18dp"
|
||||
app:full="@drawable/ic_hourglass_full_white_18dp"
|
||||
app:tint="?conversation_item_received_text_secondary_color"
|
||||
app:percentage="0"
|
||||
app:offset="0"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:alpha=".65"
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="11dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.DeliveryStatusView
|
||||
android:id="@+id/delivery_status"
|
||||
@ -141,7 +156,8 @@
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:autoLink="none"
|
||||
android:linksClickable="false"
|
||||
tools:text="Now"/>
|
||||
tools:text="Now"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<TextView android:id="@+id/sim_info"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -120,7 +120,7 @@
|
||||
android:minWidth="15sp"
|
||||
android:linksClickable="false"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:layout_gravity="right"
|
||||
android:layout_gravity="right|bottom"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:textColor="?conversation_item_sent_text_secondary_color"
|
||||
android:textSize="@dimen/conversation_item_date_text_size"
|
||||
@ -135,7 +135,7 @@
|
||||
android:minWidth="15sp"
|
||||
android:linksClickable="false"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:layout_gravity="right"
|
||||
android:layout_gravity="right|bottom"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:textColor="?conversation_item_sent_text_secondary_color"
|
||||
android:textSize="@dimen/conversation_item_date_text_size"
|
||||
@ -151,8 +151,24 @@
|
||||
android:id="@+id/delivery_status"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha=".7"
|
||||
app:iconColor="?conversation_item_sent_text_secondary_color"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.ExpirationTimerView
|
||||
android:id="@+id/expiration_indicator"
|
||||
app:empty="@drawable/ic_hourglass_empty_white_18dp"
|
||||
app:full="@drawable/ic_hourglass_full_white_18dp"
|
||||
app:tint="@color/black"
|
||||
app:percentage="0"
|
||||
app:offset="0"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:alpha=".6"
|
||||
android:layout_marginLeft="3dp"
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="11dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ImageView android:id="@+id/secure_indicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -160,10 +176,10 @@
|
||||
android:visibility="gone"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:tint="?conversation_item_sent_text_secondary_color"
|
||||
android:tintMode="multiply"
|
||||
android:contentDescription="@string/conversation_item__secure_message_description" />
|
||||
android:contentDescription="@string/conversation_item__secure_message_description"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
@ -5,6 +5,8 @@
|
||||
android:id="@+id/conversation_update_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/conversation_item_background"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:padding="20dp">
|
||||
|
@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<FrameLayout android:id="@+id/pending_indicator_stub"
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingRight="2dp"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
tools:visibility="gone"/>
|
||||
|
||||
<ImageView android:id="@+id/sent_indicator"
|
||||
android:layout_width="wrap_content"
|
||||
@ -24,6 +26,7 @@
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:visibility="gone"
|
||||
android:contentDescription="@string/conversation_item_sent__delivered_description" />
|
||||
android:contentDescription="@string/conversation_item_sent__delivered_description"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
</merge>
|
31
res/layout/expiration_dialog.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center">
|
||||
|
||||
<cn.carbswang.android.numberpickerview.library.NumberPickerView
|
||||
android:id="@+id/expiration_number_picker"
|
||||
android:layout_alignParentTop="true"
|
||||
app:npv_WrapSelectorWheel="false"
|
||||
app:npv_DividerColor="#cbc8ea"
|
||||
app:npv_TextColorSelected="@color/black"
|
||||
app:npv_ItemPaddingVertical="20dp"
|
||||
app:npv_TextColorHint="@color/grey_600"
|
||||
app:npv_TextSizeNormal="16sp"
|
||||
app:npv_TextSizeSelected="16sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView android:id="@+id/expiration_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/expiration_number_picker"
|
||||
android:minLines="2"
|
||||
android:padding="20dp"/>
|
||||
|
||||
|
||||
</RelativeLayout>
|
26
res/layout/expiration_timer_menu.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="?android:attr/actionButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_badge_icon"
|
||||
android:src="@drawable/ic_timer_white_24dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/expiration_badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:gravity="center_horizontal|bottom"
|
||||
android:paddingBottom="3dp"
|
||||
android:paddingTop="1dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="10sp" />
|
||||
</FrameLayout>
|
@ -75,6 +75,26 @@
|
||||
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:id="@+id/expires_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/message_details_table_row_pad"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Disappears"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView android:id="@+id/expires_in"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/message_details_table_row_pad"
|
||||
tools:text="1 week"/>
|
||||
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/message_details_table_row_pad">
|
||||
|
@ -4,6 +4,6 @@
|
||||
<item android:title="@string/conversation_callable_insecure__menu_call"
|
||||
android:id="@+id/menu_call_insecure"
|
||||
android:icon="?menu_call_icon"
|
||||
app:showAsAction="ifRoom" />
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
||||
|
@ -5,6 +5,6 @@
|
||||
<item android:title="@string/conversation_callable_secure__menu_call"
|
||||
android:id="@+id/menu_call_secure"
|
||||
android:icon="@drawable/ic_call_secure_white_24dp"
|
||||
app:showAsAction="ifRoom" />
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
9
res/menu/conversation_expiring_off.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:title="@string/conversation_expiring_off__disappearing_messages"
|
||||
android:id="@+id/menu_expiring_messages_off"
|
||||
android:icon="@drawable/ic_timer_off_white_24dp"/>
|
||||
|
||||
</menu>
|
10
res/menu/conversation_expiring_on.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/menu_expiring_messages"
|
||||
app:actionLayout="@layout/expiration_timer_menu"
|
||||
app:showAsAction="always"
|
||||
android:title="@string/menu_conversation_expiring_on__messages_expiring"/>
|
||||
|
||||
</menu>
|
@ -238,4 +238,19 @@
|
||||
<item>@null</item>
|
||||
</array>
|
||||
|
||||
<integer-array name="expiration_times">
|
||||
<item>0</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>30</item>
|
||||
<item>60</item>
|
||||
<item>300</item>
|
||||
<item>1800</item>
|
||||
<item>3600</item>
|
||||
<item>21600</item>
|
||||
<item>43200</item>
|
||||
<item>86400</item>
|
||||
<item>604800</item>
|
||||
</integer-array>
|
||||
|
||||
</resources>
|
||||
|
@ -166,4 +166,12 @@
|
||||
<attr name="camera" format="integer"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="HourglassView">
|
||||
<attr name="full" format="reference"/>
|
||||
<attr name="empty" format="reference"/>
|
||||
<attr name="tint" format="color"/>
|
||||
<attr name="percentage" format="integer"/>
|
||||
<attr name="offset" format="integer"/>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
@ -379,6 +379,8 @@
|
||||
<string name="MessageRecord_called_s">Called %s</string>
|
||||
<string name="MessageRecord_missed_call_from">Missed call from %s</string>
|
||||
<string name="MessageRecord_s_is_on_signal_say_hey">%s is on Signal, say hey!</string>
|
||||
<string name="MessageRecord_you">You</string>
|
||||
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set disappearing message time to %2$s</string>
|
||||
|
||||
|
||||
<!-- PassphraseChangeActivity -->
|
||||
@ -407,6 +409,11 @@
|
||||
<string name="DeviceProvisioningActivity_link_a_signal_device">Link a Signal device?</string>
|
||||
<string name="DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner">It looks like you\'re trying to link a Signal device using a 3rd party scanner. For your protection, please scan the code again from within Signal.</string>
|
||||
|
||||
<!-- ExpirationDialog -->
|
||||
<string name="ExpirationDialog_disappearing_messages">Disappearing messages</string>
|
||||
<string name="ExpirationDialog_your_messages_will_not_expire">Your messages will not expire.</string>
|
||||
<string name="ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen">Messages sent and received in this conversation will disappear %s after they have been seen.</string>
|
||||
|
||||
<!-- PassphrasePromptActivity -->
|
||||
<string name="PassphrasePromptActivity_enter_passphrase">Enter passphrase</string>
|
||||
<string name="PassphrasePromptActivity_watermark_content_description">Signal icon</string>
|
||||
@ -537,6 +544,7 @@
|
||||
<string name="ThreadRecord_missed_call">Missed call</string>
|
||||
<string name="ThreadRecord_media_message">Media message</string>
|
||||
<string name="ThreadRecord_s_is_on_signal_say_hey">%s is on Signal, say hey!</string>
|
||||
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Disappearing message time set to %s</string>
|
||||
|
||||
<!-- VerifyIdentityActivity -->
|
||||
<string name="VerifyIdentityActivity_you_do_not_have_an_identity_key">You do not have an identity key.</string>
|
||||
@ -711,10 +719,48 @@
|
||||
<string name="device_list_fragment__no_devices_linked">No devices linked...</string>
|
||||
<string name="device_list_fragment__link_new_device">Link new device</string>
|
||||
|
||||
|
||||
<!-- experience_upgrade_activity -->
|
||||
<string name="experience_upgrade_activity__continue">continue</string>
|
||||
|
||||
<!-- expiration -->
|
||||
|
||||
<string name="expiration_off">Off</string>
|
||||
|
||||
<plurals name="expiration_seconds">
|
||||
<item quantity="one">1 second</item>
|
||||
<item quantity="other">%d seconds</item>
|
||||
</plurals>
|
||||
|
||||
<string name="expiration_seconds_abbreviated">%ds</string>
|
||||
|
||||
<plurals name="expiration_minutes">
|
||||
<item quantity="one">1 minute</item>
|
||||
<item quantity="other">%d minutes</item>
|
||||
</plurals>
|
||||
|
||||
<string name="expiration_minutes_abbreviated">%dm</string>
|
||||
|
||||
<plurals name="expiration_hours">
|
||||
<item quantity="one">1 hour</item>
|
||||
<item quantity="other">%d hours</item>
|
||||
</plurals>
|
||||
|
||||
<string name="expiration_hours_abbreviated">%dh</string>
|
||||
|
||||
<plurals name="expiration_days">
|
||||
<item quantity="one">1 day</item>
|
||||
<item quantity="other">%d days</item>
|
||||
</plurals>
|
||||
|
||||
<string name="expiration_days_abbreviated">%dd</string>
|
||||
|
||||
<plurals name="expiration_weeks">
|
||||
<item quantity="one">1 week</item>
|
||||
<item quantity="other">%d weeks</item>
|
||||
</plurals>
|
||||
|
||||
<string name="expiration_weeks_abbreviated">%dw</string>
|
||||
|
||||
<!-- log_submit_activity -->
|
||||
<string name="log_submit_activity__log_fetch_failed">Could not read the log on your device. You can still use ADB to get a debug log instead.</string>
|
||||
<string name="log_submit_activity__thanks">Thanks for your help!</string>
|
||||
@ -1078,6 +1124,12 @@
|
||||
<!-- conversation_context_image -->
|
||||
<string name="conversation_context_image__save_attachment">Save attachment</string>
|
||||
|
||||
<!-- conversation_expiring_off -->
|
||||
<string name="conversation_expiring_off__disappearing_messages">Disappearing messages</string>
|
||||
|
||||
<!-- conversation_expiring_on -->
|
||||
<string name="menu_conversation_expiring_on__messages_expiring">Messages expiring</string>
|
||||
|
||||
<!-- conversation_insecure -->
|
||||
<string name="conversation_insecure__invite">Invite</string>
|
||||
|
||||
|
@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.jobs.persistence.EncryptingJobSerializer;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvider;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirementProvider;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.jobqueue.JobManager;
|
||||
import org.whispersystems.jobqueue.dependencies.DependencyInjector;
|
||||
@ -52,8 +53,9 @@ import dagger.ObjectGraph;
|
||||
*/
|
||||
public class ApplicationContext extends Application implements DependencyInjector {
|
||||
|
||||
private JobManager jobManager;
|
||||
private ObjectGraph objectGraph;
|
||||
private ExpiringMessageManager expiringMessageManager;
|
||||
private JobManager jobManager;
|
||||
private ObjectGraph objectGraph;
|
||||
|
||||
private MediaNetworkRequirementProvider mediaNetworkRequirementProvider = new MediaNetworkRequirementProvider();
|
||||
|
||||
@ -69,6 +71,7 @@ public class ApplicationContext extends Application implements DependencyInjecto
|
||||
initializeLogging();
|
||||
initializeDependencyInjection();
|
||||
initializeJobManager();
|
||||
initializeExpiringMessageManager();
|
||||
initializeGcmCheck();
|
||||
initializeSignedPreKeyCheck();
|
||||
}
|
||||
@ -84,9 +87,12 @@ public class ApplicationContext extends Application implements DependencyInjecto
|
||||
return jobManager;
|
||||
}
|
||||
|
||||
public ExpiringMessageManager getExpiringMessageManager() {
|
||||
return expiringMessageManager;
|
||||
}
|
||||
|
||||
private void initializeDeveloperBuild() {
|
||||
if (BuildConfig.DEV_BUILD) {
|
||||
// LeakCanary.install(this);
|
||||
StrictMode.setThreadPolicy(new ThreadPolicy.Builder().detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
@ -139,4 +145,8 @@ public class ApplicationContext extends Application implements DependencyInjecto
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeExpiringMessageManager() {
|
||||
this.expiringMessageManager = new ExpiringMessageManager(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,4 +15,6 @@ public interface BindableConversationItem extends Unbindable {
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipients recipients);
|
||||
|
||||
MessageRecord getMessageRecord();
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import android.os.Vibrator;
|
||||
import android.provider.Browser;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v4.view.WindowCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
@ -91,7 +92,6 @@ import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
|
||||
@ -103,6 +103,7 @@ import org.thoughtcrime.securesms.mms.AttachmentTypeSelectorAdapter;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.LocationSlide;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
@ -127,6 +128,7 @@ import org.thoughtcrime.securesms.util.DirectoryHelper.UserCapabilities;
|
||||
import org.thoughtcrime.securesms.util.DirectoryHelper.UserCapabilities.Capability;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
@ -137,7 +139,6 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -200,7 +201,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private AttachmentManager attachmentManager;
|
||||
private AudioRecorder audioRecorder;
|
||||
private BroadcastReceiver securityUpdateReceiver;
|
||||
private BroadcastReceiver groupUpdateReceiver;
|
||||
private BroadcastReceiver recipientsStaleReceiver;
|
||||
private EmojiDrawer emojiDrawer;
|
||||
protected HidingLinearLayout quickAttachmentToggle;
|
||||
private QuickAttachmentDrawer quickAttachmentDrawer;
|
||||
@ -318,9 +319,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
saveDraft();
|
||||
if (recipients != null) recipients.removeListener(this);
|
||||
if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver);
|
||||
if (groupUpdateReceiver != null) unregisterReceiver(groupUpdateReceiver);
|
||||
if (recipients != null) recipients.removeListener(this);
|
||||
if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver);
|
||||
if (recipientsStaleReceiver != null) unregisterReceiver(recipientsStaleReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@ -382,6 +383,26 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
MenuInflater inflater = this.getMenuInflater();
|
||||
menu.clear();
|
||||
|
||||
if (isSecureText) {
|
||||
if (recipients.getExpireMessages() > 0) {
|
||||
inflater.inflate(R.menu.conversation_expiring_on, menu);
|
||||
|
||||
final MenuItem item = menu.findItem(R.id.menu_expiring_messages);
|
||||
final View actionView = MenuItemCompat.getActionView(item);
|
||||
final TextView badgeView = (TextView)actionView.findViewById(R.id.expiration_badge);
|
||||
|
||||
badgeView.setText(ExpirationUtil.getExpirationAbbreviatedDisplayValue(this, recipients.getExpireMessages()));
|
||||
actionView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onOptionsItemSelected(item);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
inflater.inflate(R.menu.conversation_expiring_off, menu);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSingleConversation()) {
|
||||
if (isSecureVoice) inflater.inflate(R.menu.conversation_callable_secure, menu);
|
||||
else inflater.inflate(R.menu.conversation_callable_insecure, menu);
|
||||
@ -438,6 +459,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
|
||||
case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true;
|
||||
case R.id.menu_conversation_settings: handleConversationSettings(); return true;
|
||||
case R.id.menu_expiring_messages_off:
|
||||
case R.id.menu_expiring_messages: handleSelectMessageExpiration(); return true;
|
||||
case android.R.id.home: handleReturnToConversationList(); return true;
|
||||
}
|
||||
|
||||
@ -465,6 +488,28 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleSelectMessageExpiration() {
|
||||
ExpirationDialog.show(this, recipients.getExpireMessages(), new ExpirationDialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final int expirationTime) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
|
||||
.setExpireMessages(recipients, expirationTime);
|
||||
recipients.setExpireMessages(expirationTime);
|
||||
|
||||
OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(getRecipients(), System.currentTimeMillis(), expirationTime * 1000);
|
||||
MessageSender.send(ConversationActivity.this, masterSecret, outgoingMessage, threadId, false);
|
||||
|
||||
invalidateOptionsMenu();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleMuteNotifications() {
|
||||
MuteDialog.show(this, new MuteDialog.MuteSelectionListener() {
|
||||
@Override
|
||||
@ -547,7 +592,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
final Context context = getApplicationContext();
|
||||
|
||||
OutgoingEndSessionMessage endSessionMessage =
|
||||
new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipients(), "TERMINATE", -1));
|
||||
new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipients(), "TERMINATE", 0, -1));
|
||||
|
||||
new AsyncTask<OutgoingEndSessionMessage, Void, Long>() {
|
||||
@Override
|
||||
@ -599,7 +644,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
.setType(GroupContext.Type.QUIT)
|
||||
.build();
|
||||
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipients(), context, null, System.currentTimeMillis());
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipients(), context, null, System.currentTimeMillis(), 0);
|
||||
MessageSender.send(self, masterSecret, outgoingMessage, threadId, false);
|
||||
DatabaseFactory.getGroupDatabase(self).remove(groupId, TextSecurePreferences.getLocalNumber(self));
|
||||
initializeEnabledCheck();
|
||||
@ -979,7 +1024,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private void initializeResources() {
|
||||
if (recipients != null) recipients.removeListener(this);
|
||||
|
||||
|
||||
recipients = RecipientFactory.getRecipientsForIds(this, getIntent().getLongArrayExtra(RECIPIENTS_EXTRA), true);
|
||||
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
|
||||
archived = getIntent().getBooleanExtra(IS_ARCHIVED_EXTRA, false);
|
||||
@ -1002,6 +1047,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
titleView.setTitle(recipients);
|
||||
setBlockedUserState(recipients);
|
||||
setActionBarColor(recipients.getColor());
|
||||
invalidateOptionsMenu();
|
||||
updateRecipientPreferences();
|
||||
}
|
||||
});
|
||||
@ -1016,26 +1062,30 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
};
|
||||
|
||||
groupUpdateReceiver = new BroadcastReceiver() {
|
||||
recipientsStaleReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("ConversationActivity", "Group update received...");
|
||||
Log.w(TAG, "Group update received...");
|
||||
if (recipients != null) {
|
||||
long[] ids = recipients.getIds();
|
||||
Log.w("ConversationActivity", "Looking up new recipients...");
|
||||
Log.w(TAG, "Looking up new recipients...");
|
||||
recipients = RecipientFactory.getRecipientsForIds(context, ids, true);
|
||||
recipients.addListener(ConversationActivity.this);
|
||||
titleView.setTitle(recipients);
|
||||
onModified(recipients);
|
||||
fragment.reloadList();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter staleFilter = new IntentFilter();
|
||||
staleFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION);
|
||||
staleFilter.addAction(RecipientFactory.RECIPIENT_CLEAR_ACTION);
|
||||
|
||||
registerReceiver(securityUpdateReceiver,
|
||||
new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT),
|
||||
KeyCachingService.KEY_PERMISSION, null);
|
||||
|
||||
registerReceiver(groupUpdateReceiver,
|
||||
new IntentFilter(GroupDatabase.DATABASE_UPDATE_ACTION));
|
||||
registerReceiver(recipientsStaleReceiver, staleFilter);
|
||||
}
|
||||
|
||||
//////// Helper Methods
|
||||
@ -1281,6 +1331,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
Recipients recipients = getRecipients();
|
||||
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
|
||||
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
|
||||
long expiresIn = recipients.getExpireMessages() * 1000;
|
||||
|
||||
Log.w(TAG, "isManual Selection: " + sendButton.isManualSelection());
|
||||
Log.w(TAG, "forceSms: " + forceSms);
|
||||
@ -1292,9 +1343,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) {
|
||||
handleManualMmsRequired();
|
||||
} else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) {
|
||||
sendMediaMessage(forceSms, subscriptionId);
|
||||
sendMediaMessage(forceSms, expiresIn, subscriptionId);
|
||||
} else {
|
||||
sendTextMessage(forceSms, subscriptionId);
|
||||
sendTextMessage(forceSms, expiresIn, subscriptionId);
|
||||
}
|
||||
} catch (RecipientFormattingException ex) {
|
||||
Toast.makeText(ConversationActivity.this,
|
||||
@ -1308,13 +1359,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMediaMessage(final boolean forceSms, final int subscriptionId)
|
||||
private void sendMediaMessage(final boolean forceSms, final long expiresIn, final int subscriptionId)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), subscriptionId);
|
||||
sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), expiresIn, subscriptionId);
|
||||
}
|
||||
|
||||
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final int subscriptionId)
|
||||
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
final SettableFuture<Void> future = new SettableFuture<>();
|
||||
@ -1324,6 +1375,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
body,
|
||||
System.currentTimeMillis(),
|
||||
subscriptionId,
|
||||
expiresIn,
|
||||
distributionType);
|
||||
|
||||
if (isSecureText && !forceSms) {
|
||||
@ -1349,16 +1401,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
return future;
|
||||
}
|
||||
|
||||
private void sendTextMessage(final boolean forceSms, final int subscriptionId)
|
||||
private void sendTextMessage(final boolean forceSms, final long expiresIn, final int subscriptionId)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
final Context context = getApplicationContext();
|
||||
OutgoingTextMessage message;
|
||||
|
||||
if (isSecureText && !forceSms) {
|
||||
message = new OutgoingEncryptedMessage(recipients, getMessage());
|
||||
message = new OutgoingEncryptedMessage(recipients, getMessage(), expiresIn);
|
||||
} else {
|
||||
message = new OutgoingTextMessage(recipients, getMessage(), subscriptionId);
|
||||
message = new OutgoingTextMessage(recipients, getMessage(), expiresIn, subscriptionId);
|
||||
}
|
||||
|
||||
this.composeText.setText("");
|
||||
@ -1451,11 +1503,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
try {
|
||||
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
|
||||
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
|
||||
long expiresIn = recipients.getExpireMessages() * 1000;
|
||||
AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, ContentType.AUDIO_AAC);
|
||||
SlideDeck slideDeck = new SlideDeck();
|
||||
slideDeck.addSlide(audioSlide);
|
||||
|
||||
sendMediaMessage(forceSms, "", slideDeck, subscriptionId).addListener(new AssertedSuccessListener<Void>() {
|
||||
sendMediaMessage(forceSms, "", slideDeck, expiresIn, subscriptionId).addListener(new AssertedSuccessListener<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void nothing) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
|
@ -22,7 +22,6 @@ import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
@ -39,6 +38,8 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.VisibleForTesting;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.security.MessageDigest;
|
||||
@ -49,9 +50,6 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* A cursor adapter for a conversation thread. Ultimately
|
||||
* used by ComposeMessageActivity to display a conversation
|
||||
@ -94,8 +92,8 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
|
||||
public interface ItemClickListener {
|
||||
void onItemClick(ConversationItem item);
|
||||
void onItemLongClick(ConversationItem item);
|
||||
void onItemClick(MessageRecord item);
|
||||
void onItemLongClick(MessageRecord item);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@ -156,21 +154,23 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
@Override
|
||||
public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||
final V itemView = ViewUtil.inflate(inflater, parent, getLayoutForViewType(viewType));
|
||||
if (viewType == MESSAGE_TYPE_INCOMING || viewType == MESSAGE_TYPE_OUTGOING) {
|
||||
itemView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (clickListener != null) clickListener.onItemClick((ConversationItem)itemView);
|
||||
itemView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemClick(itemView.getMessageRecord());
|
||||
}
|
||||
});
|
||||
itemView.setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
if (clickListener != null) clickListener.onItemLongClick((ConversationItem)itemView);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
itemView.setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemLongClick(itemView.getMessageRecord());
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return new ViewHolder(itemView);
|
||||
}
|
||||
@ -195,7 +195,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
|
||||
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
|
||||
|
||||
if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined()) {
|
||||
if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined() || messageRecord.isExpirationTimerUpdate()) {
|
||||
return MESSAGE_TYPE_UPDATE;
|
||||
} else if (messageRecord.isOutgoing()) {
|
||||
return MESSAGE_TYPE_OUTGOING;
|
||||
|
@ -27,8 +27,8 @@ import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@ -57,10 +57,10 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -165,24 +165,34 @@ public class ConversationFragment extends Fragment
|
||||
if (this.recipients != null && this.threadId != -1) {
|
||||
list.setAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, null, this.recipients));
|
||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, this);
|
||||
list.getItemAnimator().setSupportsChangeAnimations(false);
|
||||
list.getItemAnimator().setMoveDuration(120);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCorrectMenuVisibility(Menu menu) {
|
||||
Set<MessageRecord> messageRecords = getListAdapter().getSelectedItems();
|
||||
boolean actionMessage = false;
|
||||
|
||||
if (actionMode != null && messageRecords.size() == 0) {
|
||||
actionMode.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
for (MessageRecord messageRecord : messageRecords) {
|
||||
if (messageRecord.isGroupAction() || messageRecord.isCallLog() ||
|
||||
messageRecord.isJoined() || messageRecord.isExpirationTimerUpdate())
|
||||
{
|
||||
actionMessage = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageRecords.size() > 1) {
|
||||
menu.findItem(R.id.menu_context_forward).setVisible(false);
|
||||
menu.findItem(R.id.menu_context_details).setVisible(false);
|
||||
menu.findItem(R.id.menu_context_save_attachment).setVisible(false);
|
||||
menu.findItem(R.id.menu_context_resend).setVisible(false);
|
||||
menu.findItem(R.id.menu_context_copy).setVisible(!actionMessage);
|
||||
} else {
|
||||
MessageRecord messageRecord = messageRecords.iterator().next();
|
||||
|
||||
@ -191,9 +201,9 @@ public class ConversationFragment extends Fragment
|
||||
!messageRecord.isMmsNotification() &&
|
||||
((MediaMmsMessageRecord)messageRecord).containsMediaSlide());
|
||||
|
||||
menu.findItem(R.id.menu_context_forward).setVisible(true);
|
||||
menu.findItem(R.id.menu_context_details).setVisible(true);
|
||||
menu.findItem(R.id.menu_context_copy).setVisible(true);
|
||||
menu.findItem(R.id.menu_context_forward).setVisible(!actionMessage);
|
||||
menu.findItem(R.id.menu_context_details).setVisible(!actionMessage);
|
||||
menu.findItem(R.id.menu_context_copy).setVisible(!actionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,9 +396,8 @@ public class ConversationFragment extends Fragment
|
||||
private class ConversationFragmentItemClickListener implements ItemClickListener {
|
||||
|
||||
@Override
|
||||
public void onItemClick(ConversationItem item) {
|
||||
public void onItemClick(MessageRecord messageRecord) {
|
||||
if (actionMode != null) {
|
||||
MessageRecord messageRecord = item.getMessageRecord();
|
||||
((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord);
|
||||
list.getAdapter().notifyDataSetChanged();
|
||||
|
||||
@ -397,9 +406,9 @@ public class ConversationFragment extends Fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemLongClick(ConversationItem item) {
|
||||
public void onItemLongClick(MessageRecord messageRecord) {
|
||||
if (actionMode == null) {
|
||||
((ConversationAdapter) list.getAdapter()).toggleSelection(item.getMessageRecord());
|
||||
((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord);
|
||||
list.getAdapter().notifyDataSetChanged();
|
||||
|
||||
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
|
||||
|
@ -23,6 +23,7 @@ import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
@ -42,6 +43,7 @@ import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||
import org.thoughtcrime.securesms.components.AlertView;
|
||||
import org.thoughtcrime.securesms.components.ExpirationTimerView;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
@ -61,6 +63,7 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
@ -110,6 +113,7 @@ public class ConversationItem extends LinearLayout
|
||||
private @NonNull AudioView audioView;
|
||||
private @NonNull Button mmsDownloadButton;
|
||||
private @NonNull TextView mmsDownloadingLabel;
|
||||
private @NonNull ExpirationTimerView expirationTimer;
|
||||
|
||||
private int defaultBubbleColor;
|
||||
|
||||
@ -151,6 +155,7 @@ public class ConversationItem extends LinearLayout
|
||||
this.bodyBubble = findViewById(R.id.body_bubble);
|
||||
this.mediaThumbnail = (ThumbnailView) findViewById(R.id.image_view);
|
||||
this.audioView = (AudioView) findViewById(R.id.audio_view);
|
||||
this.expirationTimer = (ExpirationTimerView) findViewById(R.id.expiration_indicator);
|
||||
|
||||
setOnClickListener(new ClickListener(null));
|
||||
PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
|
||||
@ -194,6 +199,7 @@ public class ConversationItem extends LinearLayout
|
||||
setMinimumWidth();
|
||||
setMediaAttributes(messageRecord);
|
||||
setSimInfo(messageRecord);
|
||||
setExpiration(messageRecord);
|
||||
}
|
||||
|
||||
private void initializeAttributes() {
|
||||
@ -211,6 +217,8 @@ public class ConversationItem extends LinearLayout
|
||||
if (recipient != null) {
|
||||
recipient.removeListener(this);
|
||||
}
|
||||
|
||||
this.expirationTimer.stopAnimation();
|
||||
}
|
||||
|
||||
public MessageRecord getMessageRecord() {
|
||||
@ -353,6 +361,36 @@ public class ConversationItem extends LinearLayout
|
||||
}
|
||||
}
|
||||
|
||||
private void setExpiration(final MessageRecord messageRecord) {
|
||||
if (messageRecord.getExpiresIn() > 0) {
|
||||
this.expirationTimer.setVisibility(View.VISIBLE);
|
||||
this.expirationTimer.setPercentage(0);
|
||||
|
||||
if (messageRecord.getExpireStarted() > 0) {
|
||||
this.expirationTimer.setExpirationTime(messageRecord.getExpireStarted(),
|
||||
messageRecord.getExpiresIn());
|
||||
this.expirationTimer.startAnimation();
|
||||
} else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
|
||||
long id = messageRecord.getId();
|
||||
boolean mms = messageRecord.isMms();
|
||||
|
||||
if (mms) DatabaseFactory.getMmsDatabase(context).markExpireStarted(id);
|
||||
else DatabaseFactory.getSmsDatabase(context).markExpireStarted(id);
|
||||
|
||||
expirationManager.scheduleDeletion(id, mms, messageRecord.getExpiresIn());
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
} else {
|
||||
this.expirationTimer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setFailedStatusIcons() {
|
||||
alertView.setFailed();
|
||||
deliveryStatusIndicator.setNone();
|
||||
|
@ -221,7 +221,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
public void onChange(boolean selfChange) {
|
||||
super.onChange(selfChange);
|
||||
Log.w(TAG, "Detected android contact data changed, refreshing cache");
|
||||
RecipientFactory.clearCache();
|
||||
RecipientFactory.clearCache(ConversationListActivity.this);
|
||||
ConversationListActivity.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -2,6 +2,9 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
@ -59,6 +62,17 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
@NonNull Recipients conversationRecipients)
|
||||
{
|
||||
bind(messageRecord, locale);
|
||||
|
||||
if (batchSelected.contains(messageRecord)) {
|
||||
setSelected(true);
|
||||
} else {
|
||||
setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageRecord getMessageRecord() {
|
||||
return messageRecord;
|
||||
}
|
||||
|
||||
private void bind(@NonNull MessageRecord messageRecord, @NonNull Locale locale) {
|
||||
@ -68,10 +82,11 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
|
||||
this.sender.addListener(this);
|
||||
|
||||
if (messageRecord.isGroupAction()) setGroupRecord(messageRecord);
|
||||
else if (messageRecord.isCallLog()) setCallRecord(messageRecord);
|
||||
else if (messageRecord.isJoined()) setJoinedRecord(messageRecord);
|
||||
else throw new AssertionError("Neither group nor log nor joined.");
|
||||
if (messageRecord.isGroupAction()) setGroupRecord(messageRecord);
|
||||
else if (messageRecord.isCallLog()) setCallRecord(messageRecord);
|
||||
else if (messageRecord.isJoined()) setJoinedRecord(messageRecord);
|
||||
else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord);
|
||||
else throw new AssertionError("Neither group nor log nor joined.");
|
||||
}
|
||||
|
||||
private void setCallRecord(MessageRecord messageRecord) {
|
||||
@ -84,8 +99,22 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
date.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setTimerRecord(final MessageRecord messageRecord) {
|
||||
if (messageRecord.getExpiresIn() > 0) {
|
||||
icon.setImageResource(R.drawable.ic_timer_white_24dp);
|
||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||
} else {
|
||||
icon.setImageResource(R.drawable.ic_timer_off_white_24dp);
|
||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||
}
|
||||
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setGroupRecord(MessageRecord messageRecord) {
|
||||
icon.setImageResource(R.drawable.ic_group_grey600_24dp);
|
||||
icon.clearColorFilter();
|
||||
|
||||
GroupUtil.getDescription(getContext(), messageRecord.getBody().getBody()).addListener(this);
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
@ -95,6 +124,7 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
|
||||
private void setJoinedRecord(MessageRecord messageRecord) {
|
||||
icon.setImageResource(R.drawable.ic_favorite_grey600_24dp);
|
||||
icon.clearColorFilter();
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setVisibility(View.GONE);
|
||||
}
|
||||
|
94
src/org/thoughtcrime/securesms/ExpirationDialog.java
Normal file
@ -0,0 +1,94 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
|
||||
import cn.carbswang.android.numberpickerview.library.NumberPickerView;
|
||||
|
||||
public class ExpirationDialog extends AlertDialog {
|
||||
|
||||
protected ExpirationDialog(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
protected ExpirationDialog(Context context, int theme) {
|
||||
super(context, theme);
|
||||
}
|
||||
|
||||
protected ExpirationDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
|
||||
super(context, cancelable, cancelListener);
|
||||
}
|
||||
|
||||
public static void show(final Context context,
|
||||
final int currentExpiration,
|
||||
final @NonNull OnClickListener listener)
|
||||
{
|
||||
final View view = createNumberPickerView(context, currentExpiration);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(context.getString(R.string.ExpirationDialog_disappearing_messages));
|
||||
builder.setView(view);
|
||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
int selected = ((NumberPickerView)view.findViewById(R.id.expiration_number_picker)).getValue();
|
||||
listener.onClick(context.getResources().getIntArray(R.array.expiration_times)[selected]);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private static View createNumberPickerView(final Context context, final int currentExpiration) {
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
final View view = inflater.inflate(R.layout.expiration_dialog, null);
|
||||
final NumberPickerView numberPickerView = (NumberPickerView)view.findViewById(R.id.expiration_number_picker);
|
||||
final TextView textView = (TextView)view.findViewById(R.id.expiration_details);
|
||||
final int[] expirationTimes = context.getResources().getIntArray(R.array.expiration_times);
|
||||
final String[] expirationDisplayValues = new String[expirationTimes.length];
|
||||
|
||||
int selectedIndex = expirationTimes.length - 1;
|
||||
|
||||
for (int i=0;i<expirationTimes.length;i++) {
|
||||
expirationDisplayValues[i] = ExpirationUtil.getExpirationDisplayValue(context, expirationTimes[i]);
|
||||
|
||||
if ((currentExpiration >= expirationTimes[i]) &&
|
||||
(i == expirationTimes.length -1 || currentExpiration < expirationTimes[i+1])) {
|
||||
selectedIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
numberPickerView.setDisplayedValues(expirationDisplayValues);
|
||||
numberPickerView.setMinValue(0);
|
||||
numberPickerView.setMaxValue(expirationTimes.length-1);
|
||||
|
||||
NumberPickerView.OnValueChangeListener listener = new NumberPickerView.OnValueChangeListener() {
|
||||
@Override
|
||||
public void onValueChange(NumberPickerView picker, int oldVal, int newVal) {
|
||||
if (newVal == 0) {
|
||||
textView.setText(R.string.ExpirationDialog_your_messages_will_not_expire);
|
||||
} else {
|
||||
textView.setText(context.getString(R.string.ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen, picker.getDisplayedValues()[newVal]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
numberPickerView.setOnValueChangedListener(listener);
|
||||
numberPickerView.setValue(selectedIndex);
|
||||
listener.onValueChange(numberPickerView, selectedIndex, selectedIndex);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
public void onClick(int expirationTime);
|
||||
}
|
||||
|
||||
}
|
@ -23,6 +23,7 @@ import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.Log;
|
||||
@ -49,6 +50,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
@ -79,9 +81,11 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
private ConversationItem conversationItem;
|
||||
private ViewGroup itemParent;
|
||||
private View metadataContainer;
|
||||
private View expiresContainer;
|
||||
private TextView errorText;
|
||||
private TextView sentDate;
|
||||
private TextView receivedDate;
|
||||
private TextView expiresInText;
|
||||
private View receivedContainer;
|
||||
private TextView transport;
|
||||
private TextView toFrom;
|
||||
@ -91,6 +95,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
private DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private boolean running;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicTheme.onCreate(this);
|
||||
@ -100,6 +106,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
|
||||
setContentView(R.layout.message_details_activity);
|
||||
running = true;
|
||||
|
||||
initializeResources();
|
||||
initializeActionBar();
|
||||
@ -122,6 +129,12 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
MessageNotifier.setVisibleThread(-1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
running = false;
|
||||
}
|
||||
|
||||
private void initializeActionBar() {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
@ -165,6 +178,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
receivedDate = (TextView ) header.findViewById(R.id.received_time);
|
||||
transport = (TextView ) header.findViewById(R.id.transport);
|
||||
toFrom = (TextView ) header.findViewById(R.id.tofrom);
|
||||
expiresContainer = header.findViewById(R.id.expires_container);
|
||||
expiresInText = (TextView) header.findViewById(R.id.expires_in);
|
||||
recipientsList.setHeaderDividersEnabled(false);
|
||||
recipientsList.addHeaderView(header, null, false);
|
||||
}
|
||||
@ -204,6 +219,29 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void updateExpirationTime(final MessageRecord messageRecord) {
|
||||
if (messageRecord.getExpiresIn() <= 0 || messageRecord.getExpireStarted() <= 0) {
|
||||
expiresContainer.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
expiresContainer.setVisibility(View.VISIBLE);
|
||||
expiresInText.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long elapsed = System.currentTimeMillis() - messageRecord.getExpireStarted();
|
||||
long remaining = messageRecord.getExpiresIn() - elapsed;
|
||||
|
||||
String duration = ExpirationUtil.getExpirationDisplayValue(MessageDetailsActivity.this, Math.max((int)(remaining / 1000), 1));
|
||||
expiresInText.setText(duration);
|
||||
|
||||
if (running) {
|
||||
expiresInText.postDelayed(this, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateRecipients(MessageRecord messageRecord, Recipients recipients) {
|
||||
final int toFromRes;
|
||||
if (messageRecord.isMms() && !messageRecord.isPush() && !messageRecord.isOutgoing()) {
|
||||
@ -233,7 +271,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private MessageRecord getMessageRecord(Context context, Cursor cursor, String type) {
|
||||
private @Nullable MessageRecord getMessageRecord(Context context, Cursor cursor, String type) {
|
||||
switch (type) {
|
||||
case MmsSmsDatabase.SMS_TRANSPORT:
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
@ -257,8 +295,13 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
final MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA));
|
||||
new MessageRecipientAsyncTask(this, messageRecord).execute();
|
||||
MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA));
|
||||
|
||||
if (messageRecord == null) {
|
||||
finish();
|
||||
} else {
|
||||
new MessageRecipientAsyncTask(this, messageRecord).execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -281,7 +324,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
private WeakReference<Context> weakContext;
|
||||
private MessageRecord messageRecord;
|
||||
|
||||
public MessageRecipientAsyncTask(Context context, MessageRecord messageRecord) {
|
||||
public MessageRecipientAsyncTask(@NonNull Context context, @NonNull MessageRecord messageRecord) {
|
||||
this.weakContext = new WeakReference<>(context);
|
||||
this.messageRecord = messageRecord;
|
||||
}
|
||||
@ -340,6 +383,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
} else {
|
||||
updateTransport(messageRecord);
|
||||
updateTime(messageRecord);
|
||||
updateExpirationTime(messageRecord);
|
||||
errorText.setVisibility(View.GONE);
|
||||
metadataContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
@ -28,6 +31,7 @@ import org.thoughtcrime.securesms.color.MaterialColors;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
|
||||
import org.thoughtcrime.securesms.preferences.AdvancedRingtonePreference;
|
||||
import org.thoughtcrime.securesms.preferences.ColorPreference;
|
||||
@ -53,10 +57,11 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private AvatarImageView avatar;
|
||||
private Toolbar toolbar;
|
||||
private TextView title;
|
||||
private TextView blockedIndicator;
|
||||
private AvatarImageView avatar;
|
||||
private Toolbar toolbar;
|
||||
private TextView title;
|
||||
private TextView blockedIndicator;
|
||||
private BroadcastReceiver staleReceiver;
|
||||
|
||||
@Override
|
||||
public void onPreCreate() {
|
||||
@ -72,6 +77,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientIds, true);
|
||||
|
||||
initializeToolbar();
|
||||
initializeReceivers();
|
||||
setHeader(recipients);
|
||||
recipients.addListener(this);
|
||||
|
||||
@ -87,6 +93,12 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
dynamicLanguage.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
unregisterReceiver(staleReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
@ -118,6 +130,23 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
this.blockedIndicator = (TextView) toolbar.findViewById(R.id.blocked_indicator);
|
||||
}
|
||||
|
||||
private void initializeReceivers() {
|
||||
this.staleReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Recipients recipients = RecipientFactory.getRecipientsForIds(context, getIntent().getLongArrayExtra(RECIPIENTS_EXTRA), true);
|
||||
recipients.addListener(RecipientPreferenceActivity.this);
|
||||
onModified(recipients);
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter staleFilter = new IntentFilter();
|
||||
staleFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION);
|
||||
staleFilter.addAction(RecipientFactory.RECIPIENT_CLEAR_ACTION);
|
||||
|
||||
registerReceiver(staleReceiver, staleFilter);
|
||||
}
|
||||
|
||||
private void setHeader(Recipients recipients) {
|
||||
this.avatar.setAvatar(recipients, true);
|
||||
this.title.setText(recipients.toShortString());
|
||||
@ -149,18 +178,15 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private Recipients recipients;
|
||||
private BroadcastReceiver staleReceiver;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
addPreferencesFromResource(R.xml.recipient_preferences);
|
||||
initializeRecipients();
|
||||
|
||||
this.recipients = RecipientFactory.getRecipientsForIds(getActivity(),
|
||||
getArguments().getLongArray(RECIPIENTS_EXTRA),
|
||||
true);
|
||||
|
||||
this.recipients.addListener(this);
|
||||
this.findPreference(PREFERENCE_TONE)
|
||||
.setOnPreferenceChangeListener(new RingtoneChangeListener());
|
||||
this.findPreference(PREFERENCE_VIBRATE)
|
||||
@ -185,6 +211,30 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
this.recipients.removeListener(this);
|
||||
getActivity().unregisterReceiver(staleReceiver);
|
||||
}
|
||||
|
||||
private void initializeRecipients() {
|
||||
this.recipients = RecipientFactory.getRecipientsForIds(getActivity(),
|
||||
getArguments().getLongArray(RECIPIENTS_EXTRA),
|
||||
true);
|
||||
|
||||
this.recipients.addListener(this);
|
||||
|
||||
this.staleReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
recipients.removeListener(RecipientPreferenceFragment.this);
|
||||
recipients = RecipientFactory.getRecipientsForIds(getActivity(), getArguments().getLongArray(RECIPIENTS_EXTRA), true);
|
||||
onModified(recipients);
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION);
|
||||
intentFilter.addAction(RecipientFactory.RECIPIENT_CLEAR_ACTION);
|
||||
|
||||
getActivity().registerReceiver(staleReceiver, intentFilter);
|
||||
}
|
||||
|
||||
private void setSummaries(Recipients recipients) {
|
||||
|
@ -0,0 +1,88 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ExpirationTimerView extends HourglassView {
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private long startedAt;
|
||||
private long expiresIn;
|
||||
|
||||
private boolean visible = false;
|
||||
private boolean stopped = true;
|
||||
|
||||
public ExpirationTimerView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ExpirationTimerView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ExpirationTimerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setExpirationTime(long startedAt, long expiresIn) {
|
||||
this.startedAt = startedAt;
|
||||
this.expiresIn = expiresIn;
|
||||
|
||||
setPercentage(calculateProgress(this.startedAt, this.expiresIn));
|
||||
}
|
||||
|
||||
public void startAnimation() {
|
||||
synchronized (this) {
|
||||
visible = true;
|
||||
if (stopped == false) return;
|
||||
else stopped = false;
|
||||
}
|
||||
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setPercentage(calculateProgress(startedAt, expiresIn));
|
||||
|
||||
|
||||
synchronized (ExpirationTimerView.this) {
|
||||
if (!visible) {
|
||||
stopped = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handler.postDelayed(this, calculateAnimationDelay(startedAt, expiresIn));
|
||||
}
|
||||
}, calculateAnimationDelay(this.startedAt, this.expiresIn));
|
||||
}
|
||||
|
||||
public void stopAnimation() {
|
||||
synchronized (this) {
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private float calculateProgress(long startedAt, long expiresIn) {
|
||||
long progressed = System.currentTimeMillis() - startedAt;
|
||||
float percentComplete = (float)progressed / (float)expiresIn;
|
||||
|
||||
return percentComplete * 100;
|
||||
}
|
||||
|
||||
private long calculateAnimationDelay(long startedAt, long expiresIn) {
|
||||
long progressed = System.currentTimeMillis() - startedAt;
|
||||
long remaining = expiresIn - progressed;
|
||||
|
||||
if (remaining < TimeUnit.SECONDS.toMillis(30)) {
|
||||
return 50;
|
||||
} else {
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
85
src/org/thoughtcrime/securesms/components/HourglassView.java
Normal file
@ -0,0 +1,85 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class HourglassView extends View {
|
||||
|
||||
private final Paint foregroundPaint;
|
||||
private final Paint backgroundPaint;
|
||||
private final Paint progressPaint;
|
||||
|
||||
private Bitmap empty;
|
||||
private Bitmap full;
|
||||
private int tint;
|
||||
|
||||
private float percentage;
|
||||
private int offset;
|
||||
|
||||
public HourglassView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HourglassView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HourglassView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.HourglassView, 0, 0);
|
||||
this.empty = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.HourglassView_empty, 0));
|
||||
this.full = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.HourglassView_full, 0));
|
||||
this.tint = typedArray.getColor(R.styleable.HourglassView_tint, 0);
|
||||
this.percentage = typedArray.getInt(R.styleable.HourglassView_percentage, 50);
|
||||
this.offset = typedArray.getInt(R.styleable.HourglassView_offset, 0);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
this.backgroundPaint = new Paint();
|
||||
this.foregroundPaint = new Paint();
|
||||
this.progressPaint = new Paint();
|
||||
|
||||
this.backgroundPaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.MULTIPLY));
|
||||
this.foregroundPaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.progressPaint.setColor(getResources().getColor(R.color.black));
|
||||
this.progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 11)
|
||||
{
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
float progressHeight = (full.getHeight() - (offset*2)) * (percentage / 100);
|
||||
|
||||
canvas.drawBitmap(full, 0, 0, backgroundPaint);
|
||||
canvas.drawRect(0, 0, full.getWidth(), offset + progressHeight, progressPaint);
|
||||
canvas.drawBitmap(empty, 0, 0, foregroundPaint);
|
||||
}
|
||||
|
||||
public void setPercentage(float percentage) {
|
||||
this.percentage = percentage;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -72,7 +72,8 @@ public class DatabaseFactory {
|
||||
private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25;
|
||||
private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26;
|
||||
private static final int INTRODUCED_SUBSCRIPTION_ID_VERSION = 27;
|
||||
private static final int DATABASE_VERSION = 27;
|
||||
private static final int INTRODUCED_EXPIRE_MESSAGES_VERSION = 28;
|
||||
private static final int DATABASE_VERSION = 28;
|
||||
|
||||
private static final String DATABASE_NAME = "messages.db";
|
||||
private static final Object lock = new Object();
|
||||
@ -820,6 +821,15 @@ public class DatabaseFactory {
|
||||
db.execSQL("ALTER TABLE mms ADD COLUMN subscription_id INTEGER DEFAULT -1");
|
||||
}
|
||||
|
||||
if (oldVersion < INTRODUCED_EXPIRE_MESSAGES_VERSION) {
|
||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN expire_messages INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE sms ADD COLUMN expires_in INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE mms ADD COLUMN expires_in INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE sms ADD COLUMN expire_started INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE mms ADD COLUMN expire_started INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE thread ADD COLUMN expires_in INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
@ -11,11 +11,9 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
@ -147,7 +145,7 @@ public class GroupDatabase extends Database {
|
||||
GROUP_ID + " = ?",
|
||||
new String[] {GroupUtil.getEncodedId(groupId)});
|
||||
|
||||
RecipientFactory.clearCache();
|
||||
RecipientFactory.clearCache(context);
|
||||
notifyDatabaseListeners();
|
||||
}
|
||||
|
||||
@ -157,7 +155,7 @@ public class GroupDatabase extends Database {
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
||||
new String[] {GroupUtil.getEncodedId(groupId)});
|
||||
|
||||
RecipientFactory.clearCache();
|
||||
RecipientFactory.clearCache(context);
|
||||
notifyDatabaseListeners();
|
||||
}
|
||||
|
||||
@ -172,7 +170,7 @@ public class GroupDatabase extends Database {
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
||||
new String[] {GroupUtil.getEncodedId(groupId)});
|
||||
|
||||
RecipientFactory.clearCache();
|
||||
RecipientFactory.clearCache(context);
|
||||
notifyDatabaseListeners();
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
@ -110,7 +111,8 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
"ct_cls" + " INTEGER, " + "resp_txt" + " TEXT, " + "d_tm" + " INTEGER, " +
|
||||
RECEIPT_COUNT + " INTEGER DEFAULT 0, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " +
|
||||
NETWORK_FAILURE + " TEXT DEFAULT NULL," + "d_rpt" + " INTEGER, " +
|
||||
SUBSCRIPTION_ID + " INTEGER DEFAULT -1);";
|
||||
SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
|
||||
EXPIRE_STARTED + " INTEGER DEFAULT 0);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||
@ -130,6 +132,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
MESSAGE_SIZE, STATUS, TRANSACTION_ID,
|
||||
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
||||
RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
|
||||
EXPIRES_IN, EXPIRE_STARTED,
|
||||
AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
@ -340,6 +343,11 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public Reader getExpireStartedMessages(@Nullable MasterSecret masterSecret) {
|
||||
String where = EXPIRE_STARTED + " > 0";
|
||||
return readerFor(masterSecret, rawQuery(where, null));
|
||||
}
|
||||
|
||||
public Reader getDecryptInProgressMessages(MasterSecret masterSecret) {
|
||||
String where = MESSAGE_BOX + " & " + (Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0";
|
||||
return readerFor(masterSecret, rawQuery(where, null));
|
||||
@ -432,6 +440,21 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
notifyConversationListeners(threadId);
|
||||
}
|
||||
|
||||
public void markExpireStarted(long messageId) {
|
||||
markExpireStarted(messageId, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void markExpireStarted(long messageId, long startedTimestamp) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(EXPIRE_STARTED, startedTimestamp);
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
|
||||
|
||||
long threadId = getThreadIdForMessage(messageId);
|
||||
notifyConversationListeners(threadId);
|
||||
}
|
||||
|
||||
public List<SyncMessageId> setMessagesRead(long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String where = THREAD_ID + " = ? AND " + READ + " = 0";
|
||||
@ -579,6 +602,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
|
||||
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
|
||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
|
||||
List<Attachment> attachments = new LinkedList<Attachment>(attachmentDatabase.getAttachmentsForMessage(messageId));
|
||||
MmsAddresses addresses = addr.getAddressesForId(messageId);
|
||||
List<String> destinations = new LinkedList<>();
|
||||
@ -591,10 +615,12 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromStrings(context, destinations, false);
|
||||
|
||||
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
|
||||
return new OutgoingGroupMediaMessage(recipients, body, attachments, timestamp);
|
||||
return new OutgoingGroupMediaMessage(recipients, body, attachments, timestamp, 0);
|
||||
} else if (Types.isExpirationTimerUpdate(outboxType)) {
|
||||
return new OutgoingExpirationUpdateMessage(recipients, timestamp, expiresIn);
|
||||
}
|
||||
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipients, body, attachments, timestamp, subscriptionId,
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipients, body, attachments, timestamp, subscriptionId, expiresIn,
|
||||
!addresses.getBcc().isEmpty() ? ThreadDatabase.DistributionTypes.BROADCAST :
|
||||
ThreadDatabase.DistributionTypes.DEFAULT);
|
||||
if (Types.isSecureType(outboxType)) {
|
||||
@ -623,6 +649,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(THREAD_ID, getThreadIdForMessage(messageId));
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
|
||||
contentValues.put(EXPIRES_IN, request.getExpiresIn());
|
||||
|
||||
List<Attachment> attachments = new LinkedList<>();
|
||||
|
||||
@ -678,6 +705,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp());
|
||||
contentValues.put(PART_COUNT, retrieved.getAttachments().size());
|
||||
contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId());
|
||||
contentValues.put(EXPIRES_IN, retrieved.getExpiresIn());
|
||||
contentValues.put(READ, 0);
|
||||
|
||||
if (!contentValues.containsKey(DATE_SENT)) {
|
||||
@ -688,8 +716,11 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
retrieved.getBody(), retrieved.getAttachments(),
|
||||
contentValues);
|
||||
|
||||
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
|
||||
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
|
||||
if (!Types.isExpirationTimerUpdate(mailbox)) {
|
||||
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
|
||||
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
|
||||
}
|
||||
|
||||
notifyConversationListeners(threadId);
|
||||
jobManager.add(new TrimThreadJob(context, threadId));
|
||||
|
||||
@ -713,6 +744,10 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
type |= Types.PUSH_MESSAGE_BIT;
|
||||
}
|
||||
|
||||
if (retrieved.isExpirationUpdate()) {
|
||||
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
|
||||
}
|
||||
|
||||
return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId, type);
|
||||
}
|
||||
|
||||
@ -733,6 +768,10 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
type |= Types.PUSH_MESSAGE_BIT;
|
||||
}
|
||||
|
||||
if (retrieved.isExpirationUpdate()) {
|
||||
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
|
||||
}
|
||||
|
||||
return insertMessageInbox(masterSecret, retrieved, "", threadId, type);
|
||||
}
|
||||
|
||||
@ -805,6 +844,10 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
else if (((OutgoingGroupMediaMessage)message).isGroupQuit()) type |= Types.GROUP_QUIT_BIT;
|
||||
}
|
||||
|
||||
if (message.isExpirationUpdate()) {
|
||||
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
|
||||
}
|
||||
|
||||
List<String> recipientNumbers = message.getRecipients().toNumberStringList(true);
|
||||
|
||||
MmsAddresses addresses;
|
||||
@ -826,6 +869,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
|
||||
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
|
||||
contentValues.put(EXPIRES_IN, message.getExpiresIn());
|
||||
|
||||
if (message.getRecipients().isSingleRecipient()) {
|
||||
try {
|
||||
@ -1118,6 +1162,8 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES));
|
||||
String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE));
|
||||
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
|
||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN));
|
||||
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED));
|
||||
|
||||
Recipients recipients = getRecipientsFor(address);
|
||||
List<IdentityKeyMismatch> mismatches = getMismatchedIdentities(mismatchDocument);
|
||||
@ -1127,7 +1173,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||
addressDeviceId, dateSent, dateReceived, receiptCount,
|
||||
threadId, body, slideDeck, partCount, box, mismatches,
|
||||
networkFailures, subscriptionId);
|
||||
networkFailures, subscriptionId, expiresIn, expireStarted);
|
||||
}
|
||||
|
||||
private Recipients getRecipientsFor(String address) {
|
||||
|
@ -14,7 +14,9 @@ public interface MmsSmsColumns {
|
||||
public static final String RECEIPT_COUNT = "delivery_receipt_count";
|
||||
public static final String MISMATCHED_IDENTITIES = "mismatched_identities";
|
||||
public static final String UNIQUE_ROW_ID = "unique_row_id";
|
||||
public static final String SUBSCRIPTION_ID = "subscription_id";
|
||||
public static final String SUBSCRIPTION_ID = "subscription_id";
|
||||
public static final String EXPIRES_IN = "expires_in";
|
||||
public static final String EXPIRE_STARTED = "expire_started";
|
||||
|
||||
public static class Types {
|
||||
protected static final long TOTAL_MASK = 0xFFFFFFFF;
|
||||
@ -61,8 +63,9 @@ public interface MmsSmsColumns {
|
||||
protected static final long PUSH_MESSAGE_BIT = 0x200000;
|
||||
|
||||
// Group Message Information
|
||||
protected static final long GROUP_UPDATE_BIT = 0x10000;
|
||||
protected static final long GROUP_QUIT_BIT = 0x20000;
|
||||
protected static final long GROUP_UPDATE_BIT = 0x10000;
|
||||
protected static final long GROUP_QUIT_BIT = 0x20000;
|
||||
protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000;
|
||||
|
||||
// Encrypted Storage Information
|
||||
protected static final long ENCRYPTION_MASK = 0xFF000000;
|
||||
@ -166,6 +169,10 @@ public interface MmsSmsColumns {
|
||||
return type == INCOMING_CALL_TYPE || type == OUTGOING_CALL_TYPE || type == MISSED_CALL_TYPE;
|
||||
}
|
||||
|
||||
public static boolean isExpirationTimerUpdate(long type) {
|
||||
return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isIncomingCall(long type) {
|
||||
return type == INCOMING_CALL_TYPE;
|
||||
}
|
||||
|
@ -33,8 +33,6 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||
|
||||
public class MmsSmsDatabase extends Database {
|
||||
|
||||
private static final String TAG = MmsSmsDatabase.class.getSimpleName();
|
||||
@ -56,7 +54,9 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
|
||||
MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsDatabase.NETWORK_FAILURE,
|
||||
MmsSmsColumns.SUBSCRIPTION_ID, TRANSPORT,
|
||||
MmsSmsColumns.SUBSCRIPTION_ID,
|
||||
MmsSmsColumns.EXPIRES_IN,
|
||||
MmsSmsColumns.EXPIRE_STARTED, TRANSPORT,
|
||||
AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
@ -147,7 +147,8 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsSmsColumns.SUBSCRIPTION_ID, MmsDatabase.NETWORK_FAILURE, TRANSPORT,
|
||||
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
AttachmentDatabase.SIZE,
|
||||
@ -171,7 +172,7 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsSmsColumns.SUBSCRIPTION_ID,
|
||||
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
@ -209,6 +210,8 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.EXPIRE_STARTED);
|
||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
|
||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
|
||||
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
|
||||
@ -240,6 +243,8 @@ public class MmsSmsDatabase extends Database {
|
||||
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
||||
smsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
|
||||
smsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID);
|
||||
smsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN);
|
||||
smsColumnsPresent.add(MmsSmsColumns.EXPIRE_STARTED);
|
||||
smsColumnsPresent.add(SmsDatabase.TYPE);
|
||||
smsColumnsPresent.add(SmsDatabase.SUBJECT);
|
||||
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
|
||||
|
@ -33,6 +33,7 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
private static final String COLOR = "color";
|
||||
private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder";
|
||||
private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id";
|
||||
private static final String EXPIRE_MESSAGES = "expire_messages";
|
||||
|
||||
public enum VibrateState {
|
||||
DEFAULT(0), ENABLED(1), DISABLED(2);
|
||||
@ -62,7 +63,8 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
MUTE_UNTIL + " INTEGER DEFAULT 0, " +
|
||||
COLOR + " TEXT DEFAULT NULL, " +
|
||||
SEEN_INVITE_REMINDER + " INTEGER DEFAULT 0, " +
|
||||
DEFAULT_SUBSCRIPTION_ID + " INTEGER DEFAULT -1);";
|
||||
DEFAULT_SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " +
|
||||
EXPIRE_MESSAGES + " INTEGER DEFAULT 0);";
|
||||
|
||||
public RecipientPreferenceDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
@ -98,6 +100,7 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
Uri notificationUri = notification == null ? null : Uri.parse(notification);
|
||||
boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1;
|
||||
int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID));
|
||||
int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRE_MESSAGES));
|
||||
|
||||
MaterialColor color;
|
||||
|
||||
@ -113,7 +116,7 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
return Optional.of(new RecipientsPreferences(blocked, muteUntil,
|
||||
VibrateState.fromId(vibrateState),
|
||||
notificationUri, color, seenInviteReminder,
|
||||
defaultSubscriptionId));
|
||||
defaultSubscriptionId, expireMessages));
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
@ -134,7 +137,6 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
updateOrInsert(recipients, values);
|
||||
}
|
||||
|
||||
|
||||
public void setBlocked(Recipients recipients, boolean blocked) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(BLOCK, blocked ? 1 : 0);
|
||||
@ -166,6 +168,14 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
updateOrInsert(recipients, values);
|
||||
}
|
||||
|
||||
public void setExpireMessages(Recipients recipients, int expiration) {
|
||||
recipients.setExpireMessages(expiration);
|
||||
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(EXPIRE_MESSAGES, expiration);
|
||||
updateOrInsert(recipients, values);
|
||||
}
|
||||
|
||||
private void updateOrInsert(Recipients recipients, ContentValues contentValues) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
@ -193,13 +203,15 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
private final MaterialColor color;
|
||||
private final boolean seenInviteReminder;
|
||||
private final int defaultSubscriptionId;
|
||||
private final int expireMessages;
|
||||
|
||||
public RecipientsPreferences(boolean blocked, long muteUntil,
|
||||
@NonNull VibrateState vibrateState,
|
||||
@Nullable Uri notification,
|
||||
@Nullable MaterialColor color,
|
||||
boolean seenInviteReminder,
|
||||
int defaultSubscriptionId)
|
||||
int defaultSubscriptionId,
|
||||
int expireMessages)
|
||||
{
|
||||
this.blocked = blocked;
|
||||
this.muteUntil = muteUntil;
|
||||
@ -208,6 +220,7 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
this.color = color;
|
||||
this.seenInviteReminder = seenInviteReminder;
|
||||
this.defaultSubscriptionId = defaultSubscriptionId;
|
||||
this.expireMessages = expireMessages;
|
||||
}
|
||||
|
||||
public @Nullable MaterialColor getColor() {
|
||||
@ -237,5 +250,9 @@ public class RecipientPreferenceDatabase extends Database {
|
||||
public Optional<Integer> getDefaultSubscriptionId() {
|
||||
return defaultSubscriptionId != -1 ? Optional.of(defaultSubscriptionId) : Optional.<Integer>absent();
|
||||
}
|
||||
|
||||
public int getExpireMessages() {
|
||||
return expireMessages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,8 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
|
||||
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
|
||||
RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
|
||||
MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1);";
|
||||
MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " +
|
||||
EXPIRES_IN + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||
@ -94,7 +95,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
|
||||
PROTOCOL, READ, STATUS, TYPE,
|
||||
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT,
|
||||
MISMATCHED_IDENTITIES, SUBSCRIPTION_ID
|
||||
MISMATCHED_IDENTITIES, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED
|
||||
};
|
||||
|
||||
private static final EarlyReceiptCache earlyReceiptCache = new EarlyReceiptCache();
|
||||
@ -235,6 +236,23 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_TYPE);
|
||||
}
|
||||
|
||||
public void markExpireStarted(long id) {
|
||||
markExpireStarted(id, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void markExpireStarted(long id, long startedAtTimestamp) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(EXPIRE_STARTED, startedAtTimestamp);
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)});
|
||||
|
||||
long threadId = getThreadIdForMessage(id);
|
||||
|
||||
DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
||||
notifyConversationListeners(threadId);
|
||||
}
|
||||
|
||||
public void markStatus(long id, int status) {
|
||||
Log.w("MessageDatabase", "Updating ID: " + id + " to status: " + status);
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@ -402,6 +420,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(READ, 0);
|
||||
contentValues.put(BODY, record.getBody().getBody());
|
||||
contentValues.put(THREAD_ID, record.getThreadId());
|
||||
contentValues.put(EXPIRES_IN, record.getExpiresIn());
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
long newMessageId = db.insert(TABLE_NAME, null, contentValues);
|
||||
@ -505,6 +524,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
values.put(PROTOCOL, message.getProtocol());
|
||||
values.put(READ, unread ? 0 : 1);
|
||||
values.put(SUBSCRIPTION_ID, message.getSubscriptionId());
|
||||
values.put(EXPIRES_IN, message.getExpiresIn());
|
||||
|
||||
if (!TextUtils.isEmpty(message.getPseudoSubject()))
|
||||
values.put(SUBJECT, message.getPseudoSubject());
|
||||
@ -552,6 +572,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(TYPE, type);
|
||||
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
|
||||
contentValues.put(EXPIRES_IN, message.getExpiresIn());
|
||||
|
||||
try {
|
||||
contentValues.put(RECEIPT_COUNT, earlyReceiptCache.remove(date, canonicalizeNumber(context, address)));
|
||||
@ -594,6 +615,12 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
return db.query(TABLE_NAME, MESSAGE_PROJECTION, selection, args, null, null, null);
|
||||
}
|
||||
|
||||
public Cursor getExpirationStartedMessages() {
|
||||
String where = EXPIRE_STARTED + " > 0";
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
return db.query(TABLE_NAME, MESSAGE_PROJECTION, where, null, null, null, null);
|
||||
}
|
||||
|
||||
public Cursor getMessage(long messageId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[]{messageId + ""},
|
||||
@ -719,6 +746,8 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT));
|
||||
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES));
|
||||
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID));
|
||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN));
|
||||
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED));
|
||||
|
||||
List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument);
|
||||
Recipients recipients = getRecipientsFor(address);
|
||||
@ -728,7 +757,8 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
recipients.getPrimaryRecipient(),
|
||||
addressDeviceId,
|
||||
dateSent, dateReceived, receiptCount, type,
|
||||
threadId, status, mismatches, subscriptionId);
|
||||
threadId, status, mismatches, subscriptionId,
|
||||
expiresIn, expireStarted);
|
||||
}
|
||||
|
||||
private Recipients getRecipientsFor(String address) {
|
||||
|
@ -67,6 +67,7 @@ public class ThreadDatabase extends Database {
|
||||
public static final String ARCHIVED = "archived";
|
||||
public static final String STATUS = "status";
|
||||
public static final String RECEIPT_COUNT = "delivery_receipt_count";
|
||||
private static final String EXPIRES_IN = "expires_in";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
|
||||
ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " +
|
||||
@ -75,7 +76,7 @@ public class ThreadDatabase extends Database {
|
||||
TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
|
||||
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " +
|
||||
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
|
||||
RECEIPT_COUNT + " INTEGER DEFAULT 0);";
|
||||
RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_IDS + ");",
|
||||
@ -133,7 +134,8 @@ public class ThreadDatabase extends Database {
|
||||
}
|
||||
|
||||
private void updateThread(long threadId, long count, String body, @Nullable Uri attachment,
|
||||
long date, int status, int receiptCount, long type, boolean unarchive)
|
||||
long date, int status, int receiptCount, long type, boolean unarchive,
|
||||
long expiresIn)
|
||||
{
|
||||
ContentValues contentValues = new ContentValues(7);
|
||||
contentValues.put(DATE, date - date % 1000);
|
||||
@ -143,6 +145,7 @@ public class ThreadDatabase extends Database {
|
||||
contentValues.put(SNIPPET_TYPE, type);
|
||||
contentValues.put(STATUS, status);
|
||||
contentValues.put(RECEIPT_COUNT, receiptCount);
|
||||
contentValues.put(EXPIRES_IN, expiresIn);
|
||||
|
||||
if (unarchive) {
|
||||
contentValues.put(ARCHIVED, 0);
|
||||
@ -503,7 +506,7 @@ public class ThreadDatabase extends Database {
|
||||
if (reader != null && (record = reader.getNext()) != null) {
|
||||
updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record),
|
||||
record.getTimestamp(), record.getDeliveryStatus(), record.getReceiptCount(),
|
||||
record.getType(), unarchive);
|
||||
record.getType(), unarchive, record.getExpiresIn());
|
||||
notifyConversationListListeners();
|
||||
return false;
|
||||
} else {
|
||||
@ -572,10 +575,12 @@ public class ThreadDatabase extends Database {
|
||||
boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0;
|
||||
int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS));
|
||||
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.RECEIPT_COUNT));
|
||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN));
|
||||
Uri snippetUri = getSnippetUri(cursor);
|
||||
|
||||
return new ThreadRecord(context, body, snippetUri, recipients, date, count, read == 1,
|
||||
threadId, receiptCount, status, type, distributionType, archived);
|
||||
threadId, receiptCount, status, type, distributionType, archived,
|
||||
expiresIn);
|
||||
}
|
||||
|
||||
private DisplayRecord.Body getPlaintextBody(Cursor cursor) {
|
||||
|
@ -115,6 +115,10 @@ public abstract class DisplayRecord {
|
||||
return isGroupUpdate() || isGroupQuit();
|
||||
}
|
||||
|
||||
public boolean isExpirationTimerUpdate() {
|
||||
return SmsDatabase.Types.isExpirationTimerUpdate(type);
|
||||
}
|
||||
|
||||
public boolean isCallLog() {
|
||||
return SmsDatabase.Types.isCallLog(type);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase.Status;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
@ -53,10 +54,12 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
||||
@NonNull SlideDeck slideDeck,
|
||||
int partCount, long mailbox,
|
||||
List<IdentityKeyMismatch> mismatches,
|
||||
List<NetworkFailure> failures, int subscriptionId)
|
||||
List<NetworkFailure> failures, int subscriptionId,
|
||||
long expiresIn, long expireStarted)
|
||||
{
|
||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId, dateSent,
|
||||
dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, mismatches, failures, subscriptionId);
|
||||
dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, mismatches, failures,
|
||||
subscriptionId, expiresIn, expireStarted);
|
||||
|
||||
this.context = context.getApplicationContext();
|
||||
this.partCount = partCount;
|
||||
@ -85,6 +88,17 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMediaPending() {
|
||||
for (Slide slide : getSlideDeck().getSlides()) {
|
||||
if (slide.isInProgress() || slide.isPendingDownload()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpannableString getDisplayBody() {
|
||||
if (MmsDatabase.Types.isDecryptInProgressType(type)) {
|
||||
|
@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
|
||||
import java.util.List;
|
||||
@ -51,6 +52,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
private final List<IdentityKeyMismatch> mismatches;
|
||||
private final List<NetworkFailure> networkFailures;
|
||||
private final int subscriptionId;
|
||||
private final long expiresIn;
|
||||
private final long expireStarted;
|
||||
|
||||
MessageRecord(Context context, long id, Body body, Recipients recipients,
|
||||
Recipient individualRecipient, int recipientDeviceId,
|
||||
@ -58,7 +61,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
int deliveryStatus, int receiptCount, long type,
|
||||
List<IdentityKeyMismatch> mismatches,
|
||||
List<NetworkFailure> networkFailures,
|
||||
int subscriptionId)
|
||||
int subscriptionId, long expiresIn, long expireStarted)
|
||||
{
|
||||
super(context, body, recipients, dateSent, dateReceived, threadId, deliveryStatus, receiptCount,
|
||||
type);
|
||||
@ -68,6 +71,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
this.mismatches = mismatches;
|
||||
this.networkFailures = networkFailures;
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
this.expireStarted = expireStarted;
|
||||
}
|
||||
|
||||
public abstract boolean isMms();
|
||||
@ -103,6 +108,10 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_missed_call_from, getIndividualRecipient().toShortString()));
|
||||
} else if (isJoined()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_s_is_on_signal_say_hey, getIndividualRecipient().toShortString()));
|
||||
} else if (isExpirationTimerUpdate()) {
|
||||
String sender = isOutgoing() ? context.getString(R.string.MessageRecord_you) : getIndividualRecipient().toShortString();
|
||||
String time = ExpirationUtil.getExpirationDisplayValue(context, (int)(getExpiresIn() / 1000));
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, sender, time));
|
||||
} else if (getBody().getBody().length() > MAX_DISPLAY_LENGTH) {
|
||||
return new SpannableString(getBody().getBody().substring(0, MAX_DISPLAY_LENGTH));
|
||||
}
|
||||
@ -155,6 +164,10 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return SmsDatabase.Types.isInvalidVersionKeyExchange(type);
|
||||
}
|
||||
|
||||
public boolean isMediaPending() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Recipient getIndividualRecipient() {
|
||||
return individualRecipient;
|
||||
}
|
||||
@ -201,4 +214,12 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
public int getSubscriptionId() {
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public long getExpireStarted() {
|
||||
return expireStarted;
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,8 @@ public class NotificationMmsMessageRecord extends MessageRecord {
|
||||
{
|
||||
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox,
|
||||
new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId);
|
||||
new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId,
|
||||
0, 0);
|
||||
|
||||
this.contentLocation = contentLocation;
|
||||
this.messageSize = messageSize;
|
||||
@ -113,6 +114,11 @@ public class NotificationMmsMessageRecord extends MessageRecord {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMediaPending() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpannableString getDisplayBody() {
|
||||
return emphasisAdded(context.getString(R.string.NotificationMmsMessageRecord_multimedia_message));
|
||||
|
@ -48,11 +48,12 @@ public class SmsMessageRecord extends MessageRecord {
|
||||
int receiptCount,
|
||||
long type, long threadId,
|
||||
int status, List<IdentityKeyMismatch> mismatches,
|
||||
int subscriptionId)
|
||||
int subscriptionId, long expiresIn, long expireStarted)
|
||||
{
|
||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, status, receiptCount, type,
|
||||
mismatches, new LinkedList<NetworkFailure>(), subscriptionId);
|
||||
mismatches, new LinkedList<NetworkFailure>(), subscriptionId,
|
||||
expiresIn, expireStarted);
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
|
@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
|
||||
/**
|
||||
@ -46,11 +47,12 @@ public class ThreadRecord extends DisplayRecord {
|
||||
private final boolean read;
|
||||
private final int distributionType;
|
||||
private final boolean archived;
|
||||
private final long expiresIn;
|
||||
|
||||
public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri,
|
||||
@NonNull Recipients recipients, long date, long count, boolean read,
|
||||
long threadId, int receiptCount, int status, long snippetType,
|
||||
int distributionType, boolean archived)
|
||||
int distributionType, boolean archived, long expiresIn)
|
||||
{
|
||||
super(context, body, recipients, date, date, threadId, status, receiptCount, snippetType);
|
||||
this.context = context.getApplicationContext();
|
||||
@ -59,6 +61,7 @@ public class ThreadRecord extends DisplayRecord {
|
||||
this.read = read;
|
||||
this.distributionType = distributionType;
|
||||
this.archived = archived;
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public @Nullable Uri getSnippetUri() {
|
||||
@ -96,6 +99,9 @@ public class ThreadRecord extends DisplayRecord {
|
||||
return emphasisAdded(context.getString(org.thoughtcrime.securesms.R.string.ThreadRecord_missed_call));
|
||||
} else if (SmsDatabase.Types.isJoinedType(type)) {
|
||||
return emphasisAdded(context.getString(R.string.ThreadRecord_s_is_on_signal_say_hey, getRecipients().getPrimaryRecipient().toShortString()));
|
||||
} else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) {
|
||||
String time = ExpirationUtil.getExpirationDisplayValue(context, (int)(getExpiresIn() / 1000));
|
||||
return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time));
|
||||
} else {
|
||||
if (TextUtils.isEmpty(getBody().getBody())) {
|
||||
return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message)));
|
||||
@ -135,4 +141,8 @@ public class ThreadRecord extends DisplayRecord {
|
||||
public int getDistributionType() {
|
||||
return distributionType;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ public class GroupManager {
|
||||
avatarAttachment = new UriAttachment(avatarUri, ContentType.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length);
|
||||
}
|
||||
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis());
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0);
|
||||
long threadId = MessageSender.send(context, masterSecret, outgoingMessage, -1, false);
|
||||
|
||||
return new GroupActionResult(groupRecipient, threadId);
|
||||
|
@ -185,7 +185,7 @@ public class GroupMessageProcessor {
|
||||
if (outgoing) {
|
||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(group.getGroupId()), false);
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipients, storage, null, envelope.getTimestamp());
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipients, storage, null, envelope.getTimestamp(), 0);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
long messageId = mmsDatabase.insertMessageOutbox(masterSecret, outgoingMessage, threadId, false);
|
||||
|
||||
@ -195,7 +195,7 @@ public class GroupMessageProcessor {
|
||||
} else {
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String body = Base64.encodeBytes(storage.toByteArray());
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group));
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group), 0);
|
||||
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage);
|
||||
|
@ -194,7 +194,7 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
|
||||
|
||||
|
||||
IncomingMediaMessage message = new IncomingMediaMessage(from, to, cc, body, retrieved.getDate() * 1000L, attachments, subscriptionId);
|
||||
IncomingMediaMessage message = new IncomingMediaMessage(from, to, cc, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(new MasterSecretUnion(masterSecret),
|
||||
message, contentLocation, threadId);
|
||||
|
@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
@ -49,10 +50,11 @@ import org.whispersystems.libsignal.LegacyMessageException;
|
||||
import org.whispersystems.libsignal.NoSessionException;
|
||||
import org.whispersystems.libsignal.UntrustedIdentityException;
|
||||
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
@ -141,6 +143,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
|
||||
if (message.isEndSession()) handleEndSessionMessage(masterSecret, envelope, message, smsMessageId);
|
||||
else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, envelope, message, smsMessageId);
|
||||
else if (message.isExpirationUpdate()) handleExpirationUpdate(masterSecret, envelope, message, smsMessageId);
|
||||
else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, envelope, message, smsMessageId);
|
||||
else handleTextMessage(masterSecret, envelope, message, smsMessageId);
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
@ -185,7 +188,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(),
|
||||
envelope.getSourceDevice(),
|
||||
message.getTimestamp(),
|
||||
"", Optional.<SignalServiceGroup>absent());
|
||||
"", Optional.<SignalServiceGroup>absent(), 0);
|
||||
|
||||
long threadId;
|
||||
|
||||
@ -218,6 +221,33 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleExpirationUpdate(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
Recipients recipients = getMessageDestination(envelope, message);
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, envelope.getSource(),
|
||||
localNumber, message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000, true,
|
||||
Optional.fromNullable(envelope.getRelay()),
|
||||
Optional.<String>absent(), message.getGroupInfo(),
|
||||
Optional.<List<SignalServiceAttachment>>absent());
|
||||
|
||||
|
||||
|
||||
database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
|
||||
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(context).setExpireMessages(recipients, message.getExpiresInSeconds());
|
||||
|
||||
if (smsMessageId.isPresent()) {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeSentMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SentTranscriptMessage message,
|
||||
@ -228,6 +258,8 @@ public class PushDecryptJob extends ContextJob {
|
||||
|
||||
if (message.getMessage().isGroupUpdate()) {
|
||||
threadId = GroupMessageProcessor.process(context, masterSecret, envelope, message.getMessage(), true);
|
||||
} else if (message.getMessage().isExpirationUpdate()) {
|
||||
threadId = handleSynchronizeSentExpirationUpdate(masterSecret, message, smsMessageId);
|
||||
} else if (message.getMessage().getAttachments().isPresent()) {
|
||||
threadId = handleSynchronizeSentMediaMessage(masterSecret, message, smsMessageId);
|
||||
} else {
|
||||
@ -275,13 +307,19 @@ public class PushDecryptJob extends ContextJob {
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
Recipients recipients = getMessageDestination(envelope, message);
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, envelope.getSource(),
|
||||
localNumber, message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000, false,
|
||||
Optional.fromNullable(envelope.getRelay()),
|
||||
message.getBody(),
|
||||
message.getGroupInfo(),
|
||||
message.getAttachments());
|
||||
|
||||
if (message.getExpiresInSeconds() != recipients.getExpireMessages()) {
|
||||
handleExpirationUpdate(masterSecret, envelope, message, Optional.<Long>absent());
|
||||
}
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
|
||||
List<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageAndThreadId.first);
|
||||
|
||||
@ -299,6 +337,40 @@ public class PushDecryptJob extends ContextJob {
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
}
|
||||
|
||||
private long handleSynchronizeSentExpirationUpdate(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Recipients recipients = getSyncMessageDestination(message);
|
||||
|
||||
OutgoingExpirationUpdateMessage expirationUpdateMessage = new OutgoingExpirationUpdateMessage(recipients,
|
||||
message.getTimestamp(),
|
||||
message.getMessage().getExpiresInSeconds() * 1000);
|
||||
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
long messageId = database.insertMessageOutbox(masterSecret, expirationUpdateMessage, threadId, false);
|
||||
|
||||
database.markAsSent(messageId);
|
||||
database.markAsPush(messageId);
|
||||
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(context).setExpireMessages(recipients, message.getMessage().getExpiresInSeconds());
|
||||
|
||||
database.markExpireStarted(messageId, message.getExpirationStartTimestamp());
|
||||
ApplicationContext.getInstance(context)
|
||||
.getExpiringMessageManager()
|
||||
.scheduleDeletion(messageId, true,
|
||||
message.getExpirationStartTimestamp(),
|
||||
message.getMessage().getExpiresInSeconds());
|
||||
|
||||
if (smsMessageId.isPresent()) {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
|
||||
}
|
||||
|
||||
return threadId;
|
||||
}
|
||||
|
||||
private long handleSynchronizeSentMediaMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
@ -308,10 +380,16 @@ public class PushDecryptJob extends ContextJob {
|
||||
Recipients recipients = getSyncMessageDestination(message);
|
||||
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(),
|
||||
PointerAttachment.forPointers(masterSecret, message.getMessage().getAttachments()),
|
||||
message.getTimestamp(), -1, ThreadDatabase.DistributionTypes.DEFAULT);
|
||||
message.getTimestamp(), -1,
|
||||
message.getMessage().getExpiresInSeconds() * 1000,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT);
|
||||
|
||||
mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);
|
||||
|
||||
if (recipients.getExpireMessages() != message.getMessage().getExpiresInSeconds()) {
|
||||
handleSynchronizeSentExpirationUpdate(masterSecret, message, Optional.<Long>absent());
|
||||
}
|
||||
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
long messageId = database.insertMessageOutbox(masterSecret, mediaMessage, threadId, false);
|
||||
|
||||
@ -328,6 +406,15 @@ public class PushDecryptJob extends ContextJob {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
|
||||
}
|
||||
|
||||
if (message.getMessage().getExpiresInSeconds() > 0) {
|
||||
database.markExpireStarted(messageId, message.getExpirationStartTimestamp());
|
||||
ApplicationContext.getInstance(context)
|
||||
.getExpiringMessageManager()
|
||||
.scheduleDeletion(messageId, true,
|
||||
message.getExpirationStartTimestamp(),
|
||||
message.getMessage().getExpiresInSeconds());
|
||||
}
|
||||
|
||||
return threadId;
|
||||
}
|
||||
|
||||
@ -335,9 +422,15 @@ public class PushDecryptJob extends ContextJob {
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String body = message.getBody().isPresent() ? message.getBody().get() : "";
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String body = message.getBody().isPresent() ? message.getBody().get() : "";
|
||||
Recipients recipients = getMessageDestination(envelope, message);
|
||||
|
||||
if (message.getExpiresInSeconds() != recipients.getExpireMessages()) {
|
||||
handleExpirationUpdate(masterSecret, envelope, message, Optional.<Long>absent());
|
||||
}
|
||||
|
||||
Pair<Long, Long> messageAndThreadId;
|
||||
|
||||
@ -347,7 +440,8 @@ public class PushDecryptJob extends ContextJob {
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(),
|
||||
envelope.getSourceDevice(),
|
||||
message.getTimestamp(), body,
|
||||
message.getGroupInfo());
|
||||
message.getGroupInfo(),
|
||||
message.getExpiresInSeconds() * 1000);
|
||||
|
||||
textMessage = new IncomingEncryptedMessage(textMessage, body);
|
||||
messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage);
|
||||
@ -361,11 +455,17 @@ public class PushDecryptJob extends ContextJob {
|
||||
private long handleSynchronizeSentTextMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
Recipients recipients = getSyncMessageDestination(message);
|
||||
String body = message.getMessage().getBody().or("");
|
||||
OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipients, body, -1);
|
||||
long expiresInMillis = message.getMessage().getExpiresInSeconds() * 1000;
|
||||
OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipients, body, expiresInMillis, -1);
|
||||
|
||||
if (recipients.getExpireMessages() != message.getMessage().getExpiresInSeconds()) {
|
||||
handleSynchronizeSentExpirationUpdate(masterSecret, message, Optional.<Long>absent());
|
||||
}
|
||||
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingTextMessage, false, message.getTimestamp());
|
||||
@ -378,6 +478,13 @@ public class PushDecryptJob extends ContextJob {
|
||||
database.deleteMessage(smsMessageId.get());
|
||||
}
|
||||
|
||||
if (expiresInMillis > 0) {
|
||||
database.markExpireStarted(messageId, message.getExpirationStartTimestamp());
|
||||
ApplicationContext.getInstance(context)
|
||||
.getExpiringMessageManager()
|
||||
.scheduleDeletion(messageId, false, message.getExpirationStartTimestamp(), expiresInMillis);
|
||||
}
|
||||
|
||||
return threadId;
|
||||
}
|
||||
|
||||
@ -470,7 +577,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
String encoded = Base64.encodeBytes(envelope.getLegacyMessage());
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(),
|
||||
envelope.getTimestamp(), encoded,
|
||||
Optional.<SignalServiceGroup>absent());
|
||||
Optional.<SignalServiceGroup>absent(), 0);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
|
||||
@ -492,7 +599,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(),
|
||||
envelope.getTimestamp(), "",
|
||||
Optional.<SignalServiceGroup>absent());
|
||||
Optional.<SignalServiceGroup>absent(), 0);
|
||||
|
||||
textMessage = new IncomingEncryptedMessage(textMessage, "");
|
||||
return database.insertMessageInbox(textMessage);
|
||||
@ -505,4 +612,12 @@ public class PushDecryptJob extends ContextJob {
|
||||
return RecipientFactory.getRecipientsFromString(context, message.getDestination().get(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private Recipients getMessageDestination(SignalServiceEnvelope envelope, SignalServiceDataMessage message) {
|
||||
if (message.getGroupInfo().isPresent()) {
|
||||
return RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId()), false);
|
||||
} else {
|
||||
return RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
@ -85,6 +87,13 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId);
|
||||
markAttachmentsUploaded(messageId, message.getAttachments());
|
||||
|
||||
if (message.getExpiresIn() > 0) {
|
||||
database.markExpireStarted(messageId);
|
||||
ApplicationContext.getInstance(context)
|
||||
.getExpiringMessageManager()
|
||||
.scheduleDeletion(messageId, true, message.getExpiresIn());
|
||||
}
|
||||
} catch (InvalidNumberException | RecipientFormattingException | UndeliverableMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsSentFailed(messageId);
|
||||
@ -152,7 +161,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
messageSender.sendMessage(addresses, groupDataMessage);
|
||||
} else {
|
||||
SignalServiceGroup group = new SignalServiceGroup(groupId);
|
||||
SignalServiceDataMessage groupMessage = new SignalServiceDataMessage(message.getSentTimeMillis(), group, attachments, message.getBody());
|
||||
SignalServiceDataMessage groupMessage = new SignalServiceDataMessage(message.getSentTimeMillis(), group,
|
||||
attachments, message.getBody(), false,
|
||||
(int)(message.getExpiresIn() / 1000),
|
||||
message.isExpirationUpdate());
|
||||
|
||||
messageSender.sendMessage(addresses, groupMessage);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
@ -62,8 +63,9 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
throws RetryLaterException, MmsException, NoSuchMessageException,
|
||||
UndeliverableMessageException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId);
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId);
|
||||
|
||||
try {
|
||||
deliver(masterSecret, message);
|
||||
@ -71,6 +73,12 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId);
|
||||
markAttachmentsUploaded(messageId, message.getAttachments());
|
||||
|
||||
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
|
||||
database.markExpireStarted(messageId);
|
||||
expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn());
|
||||
}
|
||||
|
||||
} catch (InsecureFallbackApprovalException ifae) {
|
||||
Log.w(TAG, ifae);
|
||||
database.markAsPendingInsecureSmsFallback(messageId);
|
||||
@ -122,6 +130,8 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
.withBody(message.getBody())
|
||||
.withAttachments(attachmentStreams)
|
||||
.withTimestamp(message.getSentTimeMillis())
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
.build();
|
||||
|
||||
messageSender.sendMessage(address, mediaMessage);
|
||||
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
@ -53,8 +54,9 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
|
||||
@Override
|
||||
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
|
||||
try {
|
||||
Log.w(TAG, "Sending message: " + messageId);
|
||||
@ -64,6 +66,11 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId);
|
||||
|
||||
if (record.getExpiresIn() > 0) {
|
||||
database.markExpireStarted(messageId);
|
||||
expirationManager.scheduleDeletion(record.getId(), record.isMms(), record.getExpiresIn());
|
||||
}
|
||||
|
||||
} catch (InsecureFallbackApprovalException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsPendingInsecureSmsFallback(record.getId());
|
||||
@ -108,6 +115,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withTimestamp(message.getDateSent())
|
||||
.withBody(message.getBody().getBody())
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.asEndSessionMessage(message.isEndSession())
|
||||
.build();
|
||||
|
||||
|
@ -20,6 +20,8 @@ public class IncomingMediaMessage {
|
||||
private final boolean push;
|
||||
private final long sentTimeMillis;
|
||||
private final int subscriptionId;
|
||||
private final long expiresIn;
|
||||
private final boolean expirationUpdate;
|
||||
|
||||
private final List<String> to = new LinkedList<>();
|
||||
private final List<String> cc = new LinkedList<>();
|
||||
@ -27,14 +29,17 @@ public class IncomingMediaMessage {
|
||||
|
||||
public IncomingMediaMessage(String from, List<String> to, List<String> cc,
|
||||
String body, long sentTimeMillis,
|
||||
List<Attachment> attachments, int subscriptionId)
|
||||
List<Attachment> attachments, int subscriptionId,
|
||||
long expiresIn, boolean expirationUpdate)
|
||||
{
|
||||
this.from = from;
|
||||
this.sentTimeMillis = sentTimeMillis;
|
||||
this.body = body;
|
||||
this.groupId = null;
|
||||
this.push = false;
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.from = from;
|
||||
this.sentTimeMillis = sentTimeMillis;
|
||||
this.body = body;
|
||||
this.groupId = null;
|
||||
this.push = false;
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
this.expirationUpdate = expirationUpdate;
|
||||
|
||||
this.to.addAll(to);
|
||||
this.cc.addAll(cc);
|
||||
@ -46,16 +51,20 @@ public class IncomingMediaMessage {
|
||||
String to,
|
||||
long sentTimeMillis,
|
||||
int subscriptionId,
|
||||
long expiresIn,
|
||||
boolean expirationUpdate,
|
||||
Optional<String> relay,
|
||||
Optional<String> body,
|
||||
Optional<SignalServiceGroup> group,
|
||||
Optional<List<SignalServiceAttachment>> attachments)
|
||||
{
|
||||
this.push = true;
|
||||
this.from = from;
|
||||
this.sentTimeMillis = sentTimeMillis;
|
||||
this.body = body.orNull();
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.push = true;
|
||||
this.from = from;
|
||||
this.sentTimeMillis = sentTimeMillis;
|
||||
this.body = body.orNull();
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
this.expirationUpdate = expirationUpdate;
|
||||
|
||||
if (group.isPresent()) this.groupId = GroupUtil.getEncodedId(group.get().getGroupId());
|
||||
else this.groupId = null;
|
||||
@ -88,10 +97,18 @@ public class IncomingMediaMessage {
|
||||
return push;
|
||||
}
|
||||
|
||||
public boolean isExpirationUpdate() {
|
||||
return expirationUpdate;
|
||||
}
|
||||
|
||||
public long getSentTimeMillis() {
|
||||
return sentTimeMillis;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public boolean isGroupMessage() {
|
||||
return groupId != null || to.size() > 1 || cc.size() > 0;
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage {
|
||||
|
||||
public OutgoingExpirationUpdateMessage(Recipients recipients, long sentTimeMillis, long expiresIn) {
|
||||
super(recipients, "", new LinkedList<Attachment>(), sentTimeMillis,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpirationUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -20,11 +20,12 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
||||
public OutgoingGroupMediaMessage(@NonNull Recipients recipients,
|
||||
@NonNull String encodedGroupContext,
|
||||
@NonNull List<Attachment> avatar,
|
||||
long sentTimeMillis)
|
||||
long sentTimeMillis,
|
||||
long expiresIn)
|
||||
throws IOException
|
||||
{
|
||||
super(recipients, encodedGroupContext, avatar, sentTimeMillis,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION);
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn);
|
||||
|
||||
this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext));
|
||||
}
|
||||
@ -32,12 +33,13 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
||||
public OutgoingGroupMediaMessage(@NonNull Recipients recipients,
|
||||
@NonNull GroupContext group,
|
||||
@Nullable final Attachment avatar,
|
||||
long sentTimeMillis)
|
||||
long sentTimeMillis,
|
||||
long expireIn)
|
||||
{
|
||||
super(recipients, Base64.encodeBytes(group.toByteArray()),
|
||||
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
|
||||
System.currentTimeMillis(),
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION);
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expireIn);
|
||||
|
||||
this.group = group;
|
||||
}
|
||||
|
@ -15,10 +15,11 @@ public class OutgoingMediaMessage {
|
||||
private final long sentTimeMillis;
|
||||
private final int distributionType;
|
||||
private final int subscriptionId;
|
||||
private final long expiresIn;
|
||||
|
||||
public OutgoingMediaMessage(Recipients recipients, String message,
|
||||
List<Attachment> attachments, long sentTimeMillis,
|
||||
int subscriptionId,
|
||||
int subscriptionId, long expiresIn,
|
||||
int distributionType)
|
||||
{
|
||||
this.recipients = recipients;
|
||||
@ -27,15 +28,16 @@ public class OutgoingMediaMessage {
|
||||
this.distributionType = distributionType;
|
||||
this.attachments = attachments;
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public OutgoingMediaMessage(Recipients recipients, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, int distributionType)
|
||||
public OutgoingMediaMessage(Recipients recipients, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, long expiresIn, int distributionType)
|
||||
{
|
||||
this(recipients,
|
||||
buildMessage(slideDeck, message),
|
||||
slideDeck.asAttachments(),
|
||||
sentTimeMillis, subscriptionId,
|
||||
distributionType);
|
||||
expiresIn, distributionType);
|
||||
}
|
||||
|
||||
public OutgoingMediaMessage(OutgoingMediaMessage that) {
|
||||
@ -45,6 +47,7 @@ public class OutgoingMediaMessage {
|
||||
this.attachments = that.attachments;
|
||||
this.sentTimeMillis = that.sentTimeMillis;
|
||||
this.subscriptionId = that.subscriptionId;
|
||||
this.expiresIn = that.expiresIn;
|
||||
}
|
||||
|
||||
public Recipients getRecipients() {
|
||||
@ -71,6 +74,10 @@ public class OutgoingMediaMessage {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isExpirationUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getSentTimeMillis() {
|
||||
return sentTimeMillis;
|
||||
}
|
||||
@ -79,6 +86,10 @@ public class OutgoingMediaMessage {
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
private static String buildMessage(SlideDeck slideDeck, String message) {
|
||||
if (!TextUtils.isEmpty(message) && !TextUtils.isEmpty(slideDeck.getBody())) {
|
||||
return slideDeck.getBody() + "\n\n" + message;
|
||||
|
@ -14,9 +14,10 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
|
||||
public OutgoingSecureMediaMessage(Recipients recipients, String body,
|
||||
List<Attachment> attachments,
|
||||
long sentTimeMillis,
|
||||
int distributionType)
|
||||
int distributionType,
|
||||
long expiresIn)
|
||||
{
|
||||
super(recipients, body, attachments, sentTimeMillis, -1, distributionType);
|
||||
super(recipients, body, attachments, sentTimeMillis, -1, expiresIn, distributionType);
|
||||
}
|
||||
|
||||
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {
|
||||
|
@ -72,13 +72,14 @@ public class WearReplyReceiver extends MasterSecretBroadcastReceiver {
|
||||
long threadId;
|
||||
|
||||
Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(recipientIds);
|
||||
int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1;
|
||||
int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1;
|
||||
long expiresIn = preferences.isPresent() ? preferences.get().getExpireMessages() * 1000 : 0;
|
||||
|
||||
if (recipients.isGroupRecipient()) {
|
||||
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList<Attachment>(), System.currentTimeMillis(), subscriptionId, 0);
|
||||
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList<Attachment>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0);
|
||||
threadId = MessageSender.send(context, masterSecret, reply, -1, false);
|
||||
} else {
|
||||
OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString(), subscriptionId);
|
||||
OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString(), expiresIn, subscriptionId);
|
||||
threadId = MessageSender.send(context, masterSecret, reply, -1, false);
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
package org.thoughtcrime.securesms.recipients;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@ -31,6 +32,8 @@ import java.util.StringTokenizer;
|
||||
|
||||
public class RecipientFactory {
|
||||
|
||||
public static final String RECIPIENT_CLEAR_ACTION = "org.thoughtcrime.securesms.database.RecipientFactory.CLEAR";
|
||||
|
||||
private static final RecipientProvider provider = new RecipientProvider();
|
||||
|
||||
public static Recipients getRecipientsForIds(Context context, String recipientIds, boolean asynchronous) {
|
||||
@ -133,8 +136,9 @@ public class RecipientFactory {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
public static void clearCache(Context context) {
|
||||
provider.clearCache();
|
||||
context.sendBroadcast(new Intent(RECIPIENT_CLEAR_ACTION));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -51,11 +51,12 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
|
||||
private final Set<RecipientsModifiedListener> listeners = Collections.newSetFromMap(new WeakHashMap<RecipientsModifiedListener, Boolean>());
|
||||
private final List<Recipient> recipients;
|
||||
|
||||
private Uri ringtone = null;
|
||||
private long mutedUntil = 0;
|
||||
private boolean blocked = false;
|
||||
private VibrateState vibrate = VibrateState.DEFAULT;
|
||||
private boolean stale = false;
|
||||
private Uri ringtone = null;
|
||||
private long mutedUntil = 0;
|
||||
private boolean blocked = false;
|
||||
private VibrateState vibrate = VibrateState.DEFAULT;
|
||||
private int expireMessages = 0;
|
||||
private boolean stale = false;
|
||||
|
||||
Recipients() {
|
||||
this(new LinkedList<Recipient>(), null);
|
||||
@ -65,10 +66,11 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
|
||||
this.recipients = recipients;
|
||||
|
||||
if (preferences != null) {
|
||||
ringtone = preferences.getRingtone();
|
||||
mutedUntil = preferences.getMuteUntil();
|
||||
vibrate = preferences.getVibrateState();
|
||||
blocked = preferences.isBlocked();
|
||||
ringtone = preferences.getRingtone();
|
||||
mutedUntil = preferences.getMuteUntil();
|
||||
vibrate = preferences.getVibrateState();
|
||||
blocked = preferences.isBlocked();
|
||||
expireMessages = preferences.getExpireMessages();
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,10 +81,11 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
|
||||
this.recipients = recipients;
|
||||
|
||||
if (stale != null) {
|
||||
ringtone = stale.ringtone;
|
||||
mutedUntil = stale.mutedUntil;
|
||||
vibrate = stale.vibrate;
|
||||
blocked = stale.blocked;
|
||||
ringtone = stale.ringtone;
|
||||
mutedUntil = stale.mutedUntil;
|
||||
vibrate = stale.vibrate;
|
||||
blocked = stale.blocked;
|
||||
expireMessages = stale.expireMessages;
|
||||
}
|
||||
|
||||
preferences.addListener(new FutureTaskListener<RecipientsPreferences>() {
|
||||
@ -93,10 +96,11 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
|
||||
Set<RecipientsModifiedListener> localListeners;
|
||||
|
||||
synchronized (Recipients.this) {
|
||||
ringtone = result.getRingtone();
|
||||
mutedUntil = result.getMuteUntil();
|
||||
vibrate = result.getVibrateState();
|
||||
blocked = result.isBlocked();
|
||||
ringtone = result.getRingtone();
|
||||
mutedUntil = result.getMuteUntil();
|
||||
vibrate = result.getVibrateState();
|
||||
blocked = result.isBlocked();
|
||||
expireMessages = result.getExpireMessages();
|
||||
|
||||
localListeners = new HashSet<>(listeners);
|
||||
}
|
||||
@ -178,6 +182,18 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
|
||||
else if (!isEmpty()) recipients.get(0).setColor(color);
|
||||
}
|
||||
|
||||
public synchronized int getExpireMessages() {
|
||||
return expireMessages;
|
||||
}
|
||||
|
||||
public void setExpireMessages(int expireMessages) {
|
||||
synchronized (this) {
|
||||
this.expireMessages = expireMessages;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized void addListener(RecipientsModifiedListener listener) {
|
||||
if (listeners.isEmpty()) {
|
||||
for (Recipient recipient : recipients) {
|
||||
|
@ -0,0 +1,26 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
|
||||
public class ExpirationListener extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ApplicationContext.getInstance(context).getExpiringMessageManager().checkSchedule();
|
||||
}
|
||||
|
||||
public static void setAlarm(Context context, long waitTimeMillis) {
|
||||
Intent intent = new Intent(context, ExpirationListener.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
|
||||
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
alarmManager.cancel(pendingIntent);
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + waitTimeMillis, pendingIntent);
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class ExpiringMessageManager {
|
||||
|
||||
private static final String TAG = ExpiringMessageManager.class.getSimpleName();
|
||||
|
||||
private final TreeSet<ExpiringMessageReference> expiringMessageReferences = new TreeSet<>(new ExpiringMessageComparator());
|
||||
private final Executor executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
private final SmsDatabase smsDatabase;
|
||||
private final MmsDatabase mmsDatabase;
|
||||
private final Context context;
|
||||
|
||||
public ExpiringMessageManager(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
this.mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
|
||||
executor.execute(new LoadTask());
|
||||
executor.execute(new ProcessTask());
|
||||
}
|
||||
|
||||
public void scheduleDeletion(long id, boolean mms, long expiresInMillis) {
|
||||
scheduleDeletion(id, mms, System.currentTimeMillis(), expiresInMillis);
|
||||
}
|
||||
|
||||
public void scheduleDeletion(long id, boolean mms, long startedAtTimestamp, long expiresInMillis) {
|
||||
long expiresAtMillis = startedAtTimestamp + expiresInMillis;
|
||||
|
||||
synchronized (expiringMessageReferences) {
|
||||
expiringMessageReferences.add(new ExpiringMessageReference(id, mms, expiresAtMillis));
|
||||
expiringMessageReferences.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void checkSchedule() {
|
||||
synchronized (expiringMessageReferences) {
|
||||
expiringMessageReferences.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private class LoadTask implements Runnable {
|
||||
public void run() {
|
||||
SmsDatabase.Reader smsReader = smsDatabase.readerFor(smsDatabase.getExpirationStartedMessages());
|
||||
MmsDatabase.Reader mmsReader = mmsDatabase.getExpireStartedMessages(null);
|
||||
|
||||
MessageRecord messageRecord = null;
|
||||
|
||||
while ((messageRecord = smsReader.getNext()) != null) {
|
||||
expiringMessageReferences.add(new ExpiringMessageReference(messageRecord.getId(),
|
||||
messageRecord.isMms(),
|
||||
messageRecord.getExpireStarted() + messageRecord.getExpiresIn()));
|
||||
}
|
||||
|
||||
while ((messageRecord = mmsReader.getNext()) != null) {
|
||||
expiringMessageReferences.add(new ExpiringMessageReference(messageRecord.getId(),
|
||||
messageRecord.isMms(),
|
||||
messageRecord.getExpireStarted() + messageRecord.getExpiresIn()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ProcessTask implements Runnable {
|
||||
public void run() {
|
||||
while (true) {
|
||||
ExpiringMessageReference expiredMessage = null;
|
||||
|
||||
synchronized (expiringMessageReferences) {
|
||||
try {
|
||||
while (expiringMessageReferences.isEmpty()) expiringMessageReferences.wait();
|
||||
|
||||
ExpiringMessageReference nextReference = expiringMessageReferences.first();
|
||||
long waitTime = nextReference.expiresAtMillis - System.currentTimeMillis();
|
||||
|
||||
if (waitTime > 0) {
|
||||
ExpirationListener.setAlarm(context, waitTime);
|
||||
expiringMessageReferences.wait(waitTime);
|
||||
} else {
|
||||
expiredMessage = nextReference;
|
||||
expiringMessageReferences.remove(nextReference);
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (expiredMessage != null) {
|
||||
if (expiredMessage.mms) mmsDatabase.delete(expiredMessage.id);
|
||||
else smsDatabase.deleteMessage(expiredMessage.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExpiringMessageReference {
|
||||
private final long id;
|
||||
private final boolean mms;
|
||||
private final long expiresAtMillis;
|
||||
|
||||
private ExpiringMessageReference(long id, boolean mms, long expiresAtMillis) {
|
||||
this.id = id;
|
||||
this.mms = mms;
|
||||
this.expiresAtMillis = expiresAtMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof ExpiringMessageReference)) return false;
|
||||
|
||||
ExpiringMessageReference that = (ExpiringMessageReference)other;
|
||||
return this.id == that.id && this.mms == that.mms && this.expiresAtMillis == that.expiresAtMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int)this.id ^ (mms ? 1 : 0) ^ (int)expiresAtMillis;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExpiringMessageComparator implements Comparator<ExpiringMessageReference> {
|
||||
@Override
|
||||
public int compare(ExpiringMessageReference lhs, ExpiringMessageReference rhs) {
|
||||
if (lhs.expiresAtMillis < rhs.expiresAtMillis) return -1;
|
||||
else if (lhs.expiresAtMillis > rhs.expiresAtMillis) return 1;
|
||||
else if (lhs.id < rhs.id) return -1;
|
||||
else if (lhs.id > rhs.id) return 1;
|
||||
else if (!lhs.mms && rhs.mms) return -1;
|
||||
else if (lhs.mms && !rhs.mms) return 1;
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -56,13 +56,14 @@ public class QuickResponseService extends MasterSecretIntentService {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(this, numbers, false);
|
||||
Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(this).getRecipientsPreferences(recipients.getIds());
|
||||
int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1;
|
||||
long expiresIn = preferences.isPresent() ? preferences.get().getExpireMessages() * 1000 : 0;
|
||||
|
||||
if (!TextUtils.isEmpty(content)) {
|
||||
if (recipients.isSingleRecipient()) {
|
||||
MessageSender.send(this, masterSecret, new OutgoingTextMessage(recipients, content, subscriptionId), -1, false);
|
||||
MessageSender.send(this, masterSecret, new OutgoingTextMessage(recipients, content, expiresIn, subscriptionId), -1, false);
|
||||
} else {
|
||||
MessageSender.send(this, masterSecret, new OutgoingMediaMessage(recipients, new SlideDeck(), content, System.currentTimeMillis(),
|
||||
subscriptionId, ThreadDatabase.DistributionTypes.DEFAULT), -1, false);
|
||||
subscriptionId, expiresIn, ThreadDatabase.DistributionTypes.DEFAULT), -1, false);
|
||||
}
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
|
@ -6,7 +6,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
public class IncomingJoinedMessage extends IncomingTextMessage {
|
||||
|
||||
public IncomingJoinedMessage(String sender) {
|
||||
super(sender, 1, System.currentTimeMillis(), null, Optional.<SignalServiceGroup>absent());
|
||||
super(sender, 1, System.currentTimeMillis(), null, Optional.<SignalServiceGroup>absent(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -36,6 +36,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
private final String groupId;
|
||||
private final boolean push;
|
||||
private final int subscriptionId;
|
||||
private final long expiresInMillis;
|
||||
|
||||
public IncomingTextMessage(SmsMessage message, int subscriptionId) {
|
||||
this.message = message.getDisplayMessageBody();
|
||||
@ -47,12 +48,14 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.pseudoSubject = message.getPseudoSubject();
|
||||
this.sentTimestampMillis = message.getTimestampMillis();
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresInMillis = 0;
|
||||
this.groupId = null;
|
||||
this.push = false;
|
||||
}
|
||||
|
||||
public IncomingTextMessage(String sender, int senderDeviceId, long sentTimestampMillis,
|
||||
String encodedBody, Optional<SignalServiceGroup> group)
|
||||
String encodedBody, Optional<SignalServiceGroup> group,
|
||||
long expiresInMillis)
|
||||
{
|
||||
this.message = encodedBody;
|
||||
this.sender = sender;
|
||||
@ -64,6 +67,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.sentTimestampMillis = sentTimestampMillis;
|
||||
this.push = true;
|
||||
this.subscriptionId = -1;
|
||||
this.expiresInMillis = expiresInMillis;
|
||||
|
||||
if (group.isPresent()) {
|
||||
this.groupId = GroupUtil.getEncodedId(group.get().getGroupId());
|
||||
@ -84,6 +88,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.groupId = in.readString();
|
||||
this.push = (in.readInt() == 1);
|
||||
this.subscriptionId = in.readInt();
|
||||
this.expiresInMillis = in.readLong();
|
||||
}
|
||||
|
||||
public IncomingTextMessage(IncomingTextMessage base, String newBody) {
|
||||
@ -98,6 +103,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.groupId = base.getGroupId();
|
||||
this.push = base.isPush();
|
||||
this.subscriptionId = base.getSubscriptionId();
|
||||
this.expiresInMillis = base.getExpiresIn();
|
||||
}
|
||||
|
||||
public IncomingTextMessage(List<IncomingTextMessage> fragments) {
|
||||
@ -118,6 +124,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.groupId = fragments.get(0).getGroupId();
|
||||
this.push = fragments.get(0).isPush();
|
||||
this.subscriptionId = fragments.get(0).getSubscriptionId();
|
||||
this.expiresInMillis = fragments.get(0).getExpiresIn();
|
||||
}
|
||||
|
||||
protected IncomingTextMessage(String sender, String groupId)
|
||||
@ -133,12 +140,17 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.groupId = groupId;
|
||||
this.push = true;
|
||||
this.subscriptionId = -1;
|
||||
this.expiresInMillis = 0;
|
||||
}
|
||||
|
||||
public int getSubscriptionId() {
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresInMillis;
|
||||
}
|
||||
|
||||
public long getSentTimestampMillis() {
|
||||
return sentTimestampMillis;
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -77,7 +78,7 @@ public class MessageSender {
|
||||
long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), allocatedThreadId,
|
||||
message, forceSms, System.currentTimeMillis());
|
||||
|
||||
sendTextMessage(context, recipients, forceSms, keyExchange, messageId);
|
||||
sendTextMessage(context, recipients, forceSms, keyExchange, messageId, message.getExpiresIn());
|
||||
|
||||
return allocatedThreadId;
|
||||
}
|
||||
@ -103,7 +104,7 @@ public class MessageSender {
|
||||
Recipients recipients = message.getRecipients();
|
||||
long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms);
|
||||
|
||||
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);
|
||||
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId, message.getExpiresIn());
|
||||
|
||||
return allocatedThreadId;
|
||||
} catch (MmsException e) {
|
||||
@ -124,13 +125,14 @@ public class MessageSender {
|
||||
long messageId = messageRecord.getId();
|
||||
boolean forceSms = messageRecord.isForcedSms();
|
||||
boolean keyExchange = messageRecord.isKeyExchange();
|
||||
long expiresIn = messageRecord.getExpiresIn();
|
||||
|
||||
if (messageRecord.isMms()) {
|
||||
Recipients recipients = DatabaseFactory.getMmsAddressDatabase(context).getRecipientsForId(messageId);
|
||||
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);
|
||||
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId, expiresIn);
|
||||
} else {
|
||||
Recipients recipients = messageRecord.getRecipients();
|
||||
sendTextMessage(context, recipients, forceSms, keyExchange, messageId);
|
||||
sendTextMessage(context, recipients, forceSms, keyExchange, messageId, expiresIn);
|
||||
}
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
@ -138,11 +140,12 @@ public class MessageSender {
|
||||
}
|
||||
|
||||
private static void sendMediaMessage(Context context, MasterSecret masterSecret,
|
||||
Recipients recipients, boolean forceSms, long messageId)
|
||||
Recipients recipients, boolean forceSms,
|
||||
long messageId, long expiresIn)
|
||||
throws MmsException
|
||||
{
|
||||
if (!forceSms && isSelfSend(context, recipients)) {
|
||||
sendMediaSelf(context, masterSecret, messageId);
|
||||
sendMediaSelf(context, masterSecret, messageId, expiresIn);
|
||||
} else if (isGroupPushSend(recipients)) {
|
||||
sendGroupPush(context, recipients, messageId, -1);
|
||||
} else if (!forceSms && isPushMediaSend(context, recipients)) {
|
||||
@ -153,10 +156,11 @@ public class MessageSender {
|
||||
}
|
||||
|
||||
private static void sendTextMessage(Context context, Recipients recipients,
|
||||
boolean forceSms, boolean keyExchange, long messageId)
|
||||
boolean forceSms, boolean keyExchange,
|
||||
long messageId, long expiresIn)
|
||||
{
|
||||
if (!forceSms && isSelfSend(context, recipients)) {
|
||||
sendTextSelf(context, messageId);
|
||||
sendTextSelf(context, messageId, expiresIn);
|
||||
} else if (!forceSms && isPushTextSend(context, recipients, keyExchange)) {
|
||||
sendTextPush(context, recipients, messageId);
|
||||
} else {
|
||||
@ -164,7 +168,7 @@ public class MessageSender {
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendTextSelf(Context context, long messageId) {
|
||||
private static void sendTextSelf(Context context, long messageId, long expiresIn) {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
database.markAsSent(messageId);
|
||||
@ -172,17 +176,32 @@ public class MessageSender {
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = database.copyMessageInbox(messageId);
|
||||
database.markAsPush(messageAndThreadId.first);
|
||||
|
||||
if (expiresIn > 0) {
|
||||
ExpiringMessageManager expiringMessageManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
|
||||
|
||||
database.markExpireStarted(messageId);
|
||||
expiringMessageManager.scheduleDeletion(messageId, false, expiresIn);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendMediaSelf(Context context, MasterSecret masterSecret, long messageId)
|
||||
private static void sendMediaSelf(Context context, MasterSecret masterSecret,
|
||||
long messageId, long expiresIn)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
ExpiringMessageManager expiringMessageManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
|
||||
database.markAsSent(messageId);
|
||||
database.markAsPush(messageId);
|
||||
|
||||
long newMessageId = database.copyMessageInbox(masterSecret, messageId);
|
||||
database.markAsPush(newMessageId);
|
||||
|
||||
if (expiresIn > 0) {
|
||||
database.markExpireStarted(messageId);
|
||||
expiringMessageManager.scheduleDeletion(messageId, true, expiresIn);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendTextPush(Context context, Recipients recipients, long messageId) {
|
||||
|
@ -1,12 +1,11 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
public class OutgoingEncryptedMessage extends OutgoingTextMessage {
|
||||
|
||||
public OutgoingEncryptedMessage(Recipients recipients, String body) {
|
||||
super(recipients, body, -1);
|
||||
public OutgoingEncryptedMessage(Recipients recipients, String body, long expiresIn) {
|
||||
super(recipients, body, expiresIn, -1);
|
||||
}
|
||||
|
||||
private OutgoingEncryptedMessage(OutgoingEncryptedMessage base, String body) {
|
||||
|
@ -8,19 +8,30 @@ public class OutgoingTextMessage {
|
||||
private final Recipients recipients;
|
||||
private final String message;
|
||||
private final int subscriptionId;
|
||||
private final long expiresIn;
|
||||
|
||||
public OutgoingTextMessage(Recipients recipients, String message, int subscriptionId) {
|
||||
this(recipients, message, 0, subscriptionId);
|
||||
}
|
||||
|
||||
public OutgoingTextMessage(Recipients recipients, String message, long expiresIn, int subscriptionId) {
|
||||
this.recipients = recipients;
|
||||
this.message = message;
|
||||
this.expiresIn = expiresIn;
|
||||
this.subscriptionId = subscriptionId;
|
||||
}
|
||||
|
||||
protected OutgoingTextMessage(OutgoingTextMessage base, String body) {
|
||||
this.recipients = base.getRecipients();
|
||||
this.subscriptionId = base.getSubscriptionId();
|
||||
this.expiresIn = base.getExpiresIn();
|
||||
this.message = body;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public int getSubscriptionId() {
|
||||
return subscriptionId;
|
||||
}
|
||||
@ -51,13 +62,13 @@ public class OutgoingTextMessage {
|
||||
|
||||
public static OutgoingTextMessage from(SmsMessageRecord record) {
|
||||
if (record.isSecure()) {
|
||||
return new OutgoingEncryptedMessage(record.getRecipients(), record.getBody().getBody());
|
||||
return new OutgoingEncryptedMessage(record.getRecipients(), record.getBody().getBody(), record.getExpiresIn());
|
||||
} else if (record.isKeyExchange()) {
|
||||
return new OutgoingKeyExchangeMessage(record.getRecipients(), record.getBody().getBody());
|
||||
} else if (record.isEndSession()) {
|
||||
return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody(), -1));
|
||||
return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody(), 0, -1));
|
||||
} else {
|
||||
return new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody(), record.getSubscriptionId());
|
||||
return new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody(), record.getExpiresIn(), record.getSubscriptionId());
|
||||
}
|
||||
}
|
||||
|
||||
|
50
src/org/thoughtcrime/securesms/util/ExpirationUtil.java
Normal file
@ -0,0 +1,50 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ExpirationUtil {
|
||||
|
||||
public static String getExpirationDisplayValue(Context context, int expirationTime) {
|
||||
if (expirationTime <= 0) {
|
||||
return context.getString(R.string.expiration_off);
|
||||
} else if (expirationTime < TimeUnit.MINUTES.toSeconds(1)) {
|
||||
return context.getResources().getQuantityString(R.plurals.expiration_seconds, expirationTime, expirationTime);
|
||||
} else if (expirationTime < TimeUnit.HOURS.toSeconds(1)) {
|
||||
int minutes = expirationTime / (int)TimeUnit.MINUTES.toSeconds(1);
|
||||
return context.getResources().getQuantityString(R.plurals.expiration_minutes, minutes, minutes);
|
||||
} else if (expirationTime < TimeUnit.DAYS.toSeconds(1)) {
|
||||
int hours = expirationTime / (int)TimeUnit.HOURS.toSeconds(1);
|
||||
return context.getResources().getQuantityString(R.plurals.expiration_hours, hours, hours);
|
||||
} else if (expirationTime < TimeUnit.DAYS.toSeconds(7)) {
|
||||
int days = expirationTime / (int)TimeUnit.DAYS.toSeconds(1);
|
||||
return context.getResources().getQuantityString(R.plurals.expiration_days, days, days);
|
||||
} else {
|
||||
int weeks = expirationTime / (int)TimeUnit.DAYS.toSeconds(7);
|
||||
return context.getResources().getQuantityString(R.plurals.expiration_weeks, weeks, weeks);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getExpirationAbbreviatedDisplayValue(Context context, int expirationTime) {
|
||||
if (expirationTime < TimeUnit.MINUTES.toSeconds(1)) {
|
||||
return context.getResources().getString(R.string.expiration_seconds_abbreviated, expirationTime);
|
||||
} else if (expirationTime < TimeUnit.HOURS.toSeconds(1)) {
|
||||
int minutes = expirationTime / (int)TimeUnit.MINUTES.toSeconds(1);
|
||||
return context.getResources().getString(R.string.expiration_minutes_abbreviated, minutes);
|
||||
} else if (expirationTime < TimeUnit.DAYS.toSeconds(1)) {
|
||||
int hours = expirationTime / (int)TimeUnit.HOURS.toSeconds(1);
|
||||
return context.getResources().getString(R.string.expiration_hours_abbreviated, hours);
|
||||
} else if (expirationTime < TimeUnit.DAYS.toSeconds(7)) {
|
||||
int days = expirationTime / (int)TimeUnit.DAYS.toSeconds(1);
|
||||
return context.getResources().getString(R.string.expiration_days_abbreviated, days);
|
||||
} else {
|
||||
int weeks = expirationTime / (int)TimeUnit.DAYS.toSeconds(7);
|
||||
return context.getResources().getString(R.string.expiration_weeks_abbreviated, weeks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|