Compare commits
54
Commits
f0b25f0760
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b2f7dfdb5 | ||
|
|
a34cccdf76 | ||
|
|
49789fc4a5 | ||
|
|
a6b23f0f3c | ||
|
|
47d735be2c | ||
|
|
74fb80f783 | ||
|
|
42148360e0 | ||
|
|
a32250fe34 | ||
|
|
88529c3634 | ||
|
|
8e5d88183c | ||
|
|
349eeb7025 | ||
|
|
6d131ed8e7 | ||
|
|
2f374d38c5 | ||
|
|
cfb5f5d367 | ||
|
|
117df5fedc | ||
|
|
039afc3213 | ||
|
|
2848bf3f84 | ||
|
|
70d5007e57 | ||
|
|
0965ba5984 | ||
|
|
1347bcf4df | ||
|
|
b57c41709d | ||
|
|
8af12822f8 | ||
|
|
6161d0fca1 | ||
|
|
aad904a01b | ||
|
|
2f9b1386f8 | ||
|
|
82c6ca9602 | ||
|
|
bc5b8a2d4f | ||
|
|
777797fa53 | ||
|
|
de315b7623 | ||
|
|
cf93bb5c3d | ||
|
|
a7f581e01b | ||
|
|
5d53470df3 | ||
|
|
cc819dae0b | ||
|
|
29980d714f | ||
|
|
8900099676 | ||
|
|
49c3d392ad | ||
|
|
817de4cf62 | ||
|
|
b0f8c82e37 | ||
|
|
81920e8179 | ||
|
|
3e8c4c36c6 | ||
|
|
e891b3615d | ||
|
|
96af708145 | ||
|
|
7091eb1724 | ||
|
|
2247f9f97d | ||
|
|
47b44bbd3e | ||
|
|
8a3204f485 | ||
|
|
f962801737 | ||
|
|
364eb68f5f | ||
|
|
93b5e63726 | ||
|
|
846cede0c2 | ||
|
|
842c0b5dd2 | ||
|
|
5bfddff502 | ||
|
|
55830d1d7a | ||
|
|
98492677cc |
+27
@@ -0,0 +1,27 @@
|
||||
*.iml
|
||||
.cursor/
|
||||
.gradle/
|
||||
/local.properties
|
||||
/keystore.properties
|
||||
/keystore/
|
||||
*.jks
|
||||
*.keystore
|
||||
/.idea/
|
||||
.DS_Store
|
||||
/build/
|
||||
/captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
app/build/
|
||||
modules/*/build/
|
||||
modules/*/.git/
|
||||
modules/*/sample/
|
||||
*.apk
|
||||
*.ap_
|
||||
*.dex
|
||||
*.class
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
tools/messaging_*.png
|
||||
tools/stitch-*.txt
|
||||
+7
-3
@@ -33,6 +33,7 @@ messaging_java_defaults {
|
||||
soong_config_variables: {
|
||||
build_variant_eng: {
|
||||
optimize: {
|
||||
keep_runtime_invisible_annotations: true,
|
||||
proguard_flags_files: [
|
||||
"proguard.flags",
|
||||
"proguard-test.flags",
|
||||
@@ -40,6 +41,7 @@ messaging_java_defaults {
|
||||
},
|
||||
conditions_default: {
|
||||
optimize: {
|
||||
keep_runtime_invisible_annotations: true,
|
||||
proguard_flags_files: [
|
||||
"proguard.flags",
|
||||
"proguard-release.flags",
|
||||
@@ -85,11 +87,13 @@ android_app {
|
||||
"1.0.001",
|
||||
"--version-code",
|
||||
"10001040",
|
||||
"--rename-manifest-package",
|
||||
"com.bnkode.notie",
|
||||
],
|
||||
required: [
|
||||
"libframesequence",
|
||||
"libgiftranscode",
|
||||
"com.android.messaging.allowlist",
|
||||
"com.bnkode.notie.allowlist",
|
||||
],
|
||||
optimize: {
|
||||
obfuscate: true,
|
||||
@@ -103,9 +107,9 @@ android_app {
|
||||
}
|
||||
|
||||
prebuilt_etc {
|
||||
name: "com.android.messaging.allowlist",
|
||||
name: "com.bnkode.notie.allowlist",
|
||||
product_specific: true,
|
||||
sub_dir: "sysconfig",
|
||||
src: "com.android.messaging.allowlist.xml",
|
||||
src: "com.bnkode.notie.allowlist.xml",
|
||||
filename_from_src: true,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# Default code reviewers picked from top 3 or more developers.
|
||||
# Please update this list if you find better candidates.
|
||||
tomtaylor@google.com
|
||||
rtenneti@google.com
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('keystore.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.android.messaging'
|
||||
compileSdk 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.bnkode.notie'
|
||||
minSdk 33
|
||||
targetSdk 34
|
||||
versionCode 10001040
|
||||
versionName '1.0.001'
|
||||
vectorDrawables.useSupportLibrary true
|
||||
resourceConfigurations += ['en', 'ko']
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
storeFile rootProject.file(keystoreProperties['storeFile'])
|
||||
storePassword keystoreProperties['storePassword']
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.release
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
|
||||
rootProject.file('proguard-release.flags'),
|
||||
rootProject.file('proguard.flags')
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'src/main/AndroidManifest.xml'
|
||||
java.srcDirs = ['src/main/java']
|
||||
res.srcDirs = ['src/main/res']
|
||||
assets.srcDirs = ['src/main/assets']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':modules:colorpicker')
|
||||
implementation project(':modules:chips')
|
||||
implementation project(':modules:photoviewer')
|
||||
implementation project(':modules:vcard')
|
||||
|
||||
implementation 'androidx.activity:activity:1.9.3'
|
||||
implementation 'androidx.annotation:annotation:1.8.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'androidx.collection:collection:1.4.4'
|
||||
implementation 'androidx.core:core:1.15.0'
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.7'
|
||||
implementation 'androidx.fragment:fragment:1.8.5'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.8.7'
|
||||
implementation 'androidx.media:media:1.7.0'
|
||||
implementation 'androidx.palette:palette:1.0.0'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.viewpager:viewpager:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'com.google.guava:guava:33.3.1-android'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.13.50'
|
||||
}
|
||||
|
||||
def messagingAvdSerial = rootProject.findProperty('messaging.avd.serial') ?: 'emulator-5556'
|
||||
def adbExecutable = new File(android.sdkDirectory, 'platform-tools/adb.exe')
|
||||
|
||||
tasks.register('installMessaging', Exec) {
|
||||
group = 'install'
|
||||
description = "Install debug APK on ${messagingAvdSerial} only"
|
||||
dependsOn 'assembleDebug'
|
||||
def apk = layout.buildDirectory.file('outputs/apk/debug/app-debug.apk').get().asFile
|
||||
commandLine adbExecutable.absolutePath, '-s', messagingAvdSerial, 'install', '-r', apk.absolutePath
|
||||
}
|
||||
|
||||
tasks.register('launchMessaging', Exec) {
|
||||
group = 'install'
|
||||
description = "Launch Messaging on ${messagingAvdSerial}"
|
||||
dependsOn 'installMessaging'
|
||||
commandLine adbExecutable.absolutePath, '-s', messagingAvdSerial, 'shell', 'am', 'start',
|
||||
'-n', 'com.bnkode.notie/com.android.messaging.ui.conversationlist.ConversationListActivity'
|
||||
}
|
||||
@@ -19,10 +19,6 @@
|
||||
package="com.android.messaging"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="33"
|
||||
android:targetSdkVersion="34" />
|
||||
|
||||
<!-- Application holds CPU wakelock while working in background -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- Application needs SMS/MMS permissions -->
|
||||
@@ -48,6 +44,12 @@
|
||||
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||
|
||||
<!-- Optional features -->
|
||||
<uses-feature
|
||||
@@ -68,6 +70,12 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.location"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="false" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
@@ -83,7 +91,9 @@
|
||||
android:allowBackup="false"
|
||||
android:appCategory="social"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/BugleTheme">
|
||||
@@ -136,10 +146,18 @@
|
||||
<activity
|
||||
android:name=".ui.conversationlist.ArchivedConversationListActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/archived_activity_title"
|
||||
android:label="@string/home_archived_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme.ArchivedConversationListActivity" />
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.conversationlist.RecentlyDeletedConversationListActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_recently_deleted_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<!-- Displays the contents of a single conversation -->
|
||||
<activity
|
||||
@@ -159,6 +177,81 @@
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeSpamActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_spam_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeAutomationActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_automation_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeAutomationCreateActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_automation_create_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:theme="@style/Home.BottomSheet"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeSearchActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_search_field_hint"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen"
|
||||
android:windowSoftInputMode="adjustResize|stateVisible" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeComposeActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_compose_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:theme="@style/Home.BottomSheet"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeGroupNameActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_group_name_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:theme="@style/Home.BottomSheet"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeLocationActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_location_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeSecuritySettingsActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_security_title"
|
||||
android:parentActivityName="com.android.messaging.ui.appsettings.ApplicationSettingsActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeAppLockActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:excludeFromRecents="true"
|
||||
android:label="@string/home_settings_app_lock"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<!-- Full-screen photo viewer -->
|
||||
<activity
|
||||
android:name=".ui.photoviewer.BuglePhotoViewActivity"
|
||||
@@ -175,7 +268,7 @@
|
||||
android:label="@string/settings_activity_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme.SettingsActivity" />
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.appsettings.PerSubscriptionSettingsActivity"
|
||||
@@ -183,7 +276,7 @@
|
||||
android:label="@string/advanced_settings_activity_title"
|
||||
android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme.SettingsActivity" />
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.appsettings.ApplicationSettingsActivity"
|
||||
@@ -192,7 +285,7 @@
|
||||
android:label="@string/general_settings_activity_title"
|
||||
android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme.SettingsActivity">
|
||||
android:theme="@style/Home.SubScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -227,22 +320,40 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- People & Options -->
|
||||
<!-- Conversation profile -->
|
||||
<activity
|
||||
android:name=".ui.conversationsettings.PeopleAndOptionsActivity"
|
||||
android:name=".ui.home.HomeProfileActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/people_and_options_activity_title"
|
||||
android:label="@string/home_profile_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversation.ConversationActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme" />
|
||||
android:theme="@style/Home.SubScreen"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
|
||||
|
||||
<!-- Conversation search -->
|
||||
<activity
|
||||
android:name=".ui.home.HomeConversationSearchActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_conversation_search_hint"
|
||||
android:parentActivityName="com.android.messaging.ui.conversation.ConversationActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen"
|
||||
android:windowSoftInputMode="adjustResize|stateVisible" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeMessageDetailsActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_message_details_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversation.ConversationActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<!-- License -->
|
||||
<activity
|
||||
android:name=".ui.LicenseActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/menu_license"
|
||||
android:theme="@android:style/Theme.Holo.Light.Dialog">
|
||||
</activity>
|
||||
android:label="@string/home_settings_licenses"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<!-- Message Forwarding -->
|
||||
<activity
|
||||
@@ -263,7 +374,8 @@
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Invisible">
|
||||
android:theme="@style/Home.TransparentSheetHost"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<!-- VCard details -->
|
||||
@@ -286,20 +398,20 @@
|
||||
|
||||
<provider
|
||||
android:name=".datamodel.MessagingContentProvider"
|
||||
android:authorities="com.android.messaging.datamodel.MessagingContentProvider"
|
||||
android:authorities="com.bnkode.notie.datamodel.MessagingContentProvider"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name">
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name=".datamodel.MmsFileProvider"
|
||||
android:authorities="com.android.messaging.datamodel.MmsFileProvider"
|
||||
android:authorities="com.bnkode.notie.datamodel.MmsFileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
<provider
|
||||
android:name=".datamodel.MediaScratchFileProvider"
|
||||
android:authorities="com.android.messaging.datamodel.MediaScratchFileProvider"
|
||||
android:authorities="com.bnkode.notie.datamodel.MediaScratchFileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
@@ -355,18 +467,18 @@
|
||||
android:name=".receiver.SendStatusReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.receiver.SendStatusReceiver.MESSAGE_SENT" />
|
||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_SENT" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.receiver.SendStatusReceiver.MESSAGE_DELIVERED" />
|
||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_DELIVERED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.receiver.SendStatusReceiver.MMS_SENT" />
|
||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MMS_SENT" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.receiver.SendStatusReceiver.MMS_DOWNLOADED" />
|
||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MMS_DOWNLOADED" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
@@ -405,6 +517,14 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.ScheduledMessageReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.bnkode.notie.SEND_SCHEDULED_MESSAGE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Broadcast receiver that will be notified to reset notifications -->
|
||||
<receiver
|
||||
android:name=".receiver.NotificationReceiver"
|
||||
@@ -416,7 +536,7 @@
|
||||
android:name=".datamodel.action.ActionServiceImpl$PendingActionReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.datamodel.PENDING_ACTION" />
|
||||
<action android:name="com.bnkode.notie.datamodel.PENDING_ACTION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
||||
<style>
|
||||
html, body, #map { height: 100%; margin: 0; padding: 0; background: #f4f1fb; }
|
||||
.leaflet-control-attribution { font-size: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script>
|
||||
var map = L.map('map', { zoomControl: false }).setView([37.5665, 126.9780], 16);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© OpenStreetMap'
|
||||
}).addTo(map);
|
||||
function notify() {
|
||||
var c = map.getCenter();
|
||||
if (window.LocationBridge) {
|
||||
LocationBridge.onMapMoved(c.lat, c.lng);
|
||||
}
|
||||
}
|
||||
map.on('moveend', notify);
|
||||
function setCenter(lat, lng) {
|
||||
map.setView([lat, lng], Math.max(map.getZoom(), 16));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+1
-154
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2024 The LineageOS Project
|
||||
* Copyright (C) 2024-2025 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,6 @@
|
||||
package android.support.v7.mms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.net.Uri;
|
||||
@@ -28,8 +26,6 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.messaging.R;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
@@ -112,95 +108,6 @@ class DefaultApnSettingsLoader implements ApnSettingsLoader {
|
||||
public void setSuccess() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public boolean equals(final BaseApn other) {
|
||||
return TextUtils.equals(mMmsc, other.getMmsc()) &&
|
||||
TextUtils.equals(mMmsProxy, other.getMmsProxy()) &&
|
||||
mMmsProxyPort == other.getMmsProxyPort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An in-memory implementation of an APN. These APNs are organized into an in-memory list.
|
||||
* The order of the list can be changed by the setSuccess method.
|
||||
*/
|
||||
private static class MemoryApn implements Apn {
|
||||
/**
|
||||
* Create an in-memory APN loaded from resources
|
||||
*
|
||||
* @param apns the in-memory APN list
|
||||
* @param typesIn the APN type field
|
||||
* @param mmscIn the APN mmsc field
|
||||
* @param proxyIn the APN mmsproxy field
|
||||
* @param portIn the APN mmsport field
|
||||
* @return an in-memory APN instance, null if there is invalid parameter
|
||||
*/
|
||||
public static MemoryApn from(final List<Apn> apns, final String typesIn,
|
||||
final String mmscIn, final String proxyIn, final String portIn) {
|
||||
if (apns == null) {
|
||||
return null;
|
||||
}
|
||||
final BaseApn base = BaseApn.from(typesIn, mmscIn, proxyIn, portIn);
|
||||
if (base == null) {
|
||||
return null;
|
||||
}
|
||||
for (final Apn apn : apns) {
|
||||
if (apn instanceof MemoryApn && ((MemoryApn) apn).equals(base)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return new MemoryApn(apns, base);
|
||||
}
|
||||
|
||||
private final List<Apn> mApns;
|
||||
private final BaseApn mBase;
|
||||
|
||||
public MemoryApn(final List<Apn> apns, final BaseApn base) {
|
||||
mApns = apns;
|
||||
mBase = base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMmsc() {
|
||||
return mBase.getMmsc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMmsProxy() {
|
||||
return mBase.getMmsProxy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMmsProxyPort() {
|
||||
return mBase.getMmsProxyPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuccess() {
|
||||
// If this is being marked as a successful APN, move it to the top of the list so
|
||||
// next time it will be tried first
|
||||
boolean moved = false;
|
||||
synchronized (mApns) {
|
||||
if (mApns.get(0) != this) {
|
||||
mApns.remove(this);
|
||||
mApns.add(0, this);
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
if (moved) {
|
||||
Log.d(MmsService.TAG, "Set APN ["
|
||||
+ "MMSC=" + getMmsc() + ", "
|
||||
+ "PROXY=" + getMmsProxy() + ", "
|
||||
+ "PORT=" + getMmsProxyPort() + "] to be first");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(final BaseApn other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
return mBase.equals(other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,14 +129,6 @@ class DefaultApnSettingsLoader implements ApnSettingsLoader {
|
||||
private static final int COLUMN_MMSPROXY = 2;
|
||||
private static final int COLUMN_MMSPORT = 3;
|
||||
|
||||
private static final String APN_MCC = "mcc";
|
||||
private static final String APN_MNC = "mnc";
|
||||
private static final String APN_APN = "apn";
|
||||
private static final String APN_TYPE = "type";
|
||||
private static final String APN_MMSC = "mmsc";
|
||||
private static final String APN_MMSPROXY = "mmsproxy";
|
||||
private static final String APN_MMSPORT = "mmsport";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
// Cached APNs for subIds
|
||||
@@ -263,16 +162,6 @@ class DefaultApnSettingsLoader implements ApnSettingsLoader {
|
||||
private void loadLocked(final int subId, final String apnName, final List<Apn> apns) {
|
||||
// Try system APN table first
|
||||
loadFromSystem(subId, apnName, apns);
|
||||
if (apns.size() > 0) {
|
||||
return;
|
||||
}
|
||||
// Try loading from apns.xml in resources
|
||||
loadFromResources(subId, apnName, apns);
|
||||
if (apns.size() > 0) {
|
||||
return;
|
||||
}
|
||||
// Try resources but without APN name
|
||||
loadFromResources(subId, null/*apnName*/, apns);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,48 +273,6 @@ class DefaultApnSettingsLoader implements ApnSettingsLoader {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find matching APNs using builtin APN list resource
|
||||
*
|
||||
* @param subId the SIM subId
|
||||
* @param apnName the APN name to match
|
||||
* @param apns the list for returning results
|
||||
*/
|
||||
private void loadFromResources(final int subId, final String apnName, final List<Apn> apns) {
|
||||
Log.i(MmsService.TAG, "Loading APNs from resources, apnName=" + apnName);
|
||||
final int[] mccMnc = Utils.getMccMnc(mContext, subId);
|
||||
if (mccMnc[0] == 0) {
|
||||
Log.w(MmsService.TAG, "Can not get valid mcc/mnc from system");
|
||||
return;
|
||||
}
|
||||
// MCC/MNC is good, loading/querying APNs from XML
|
||||
try (XmlResourceParser xml = mContext.getResources().getXml(R.xml.apns)) {
|
||||
new ApnsXmlParser(xml, apnValues -> {
|
||||
final String mcc = trimWithNullCheck(apnValues.getAsString(APN_MCC));
|
||||
final String mnc = trimWithNullCheck(apnValues.getAsString(APN_MNC));
|
||||
final String apn = trimWithNullCheck(apnValues.getAsString(APN_APN));
|
||||
try {
|
||||
if (mccMnc[0] == Integer.parseInt(mcc) &&
|
||||
mccMnc[1] == Integer.parseInt(mnc) &&
|
||||
(TextUtils.isEmpty(apnName) || apnName.equalsIgnoreCase(apn))) {
|
||||
final String type = apnValues.getAsString(APN_TYPE);
|
||||
final String mmsc = apnValues.getAsString(APN_MMSC);
|
||||
final String mmsproxy = apnValues.getAsString(APN_MMSPROXY);
|
||||
final String mmsport = apnValues.getAsString(APN_MMSPORT);
|
||||
final Apn newApn = MemoryApn.from(apns, type, mmsc, mmsproxy, mmsport);
|
||||
if (newApn != null) {
|
||||
apns.add(newApn);
|
||||
}
|
||||
}
|
||||
} catch (final NumberFormatException e) {
|
||||
// Ignore
|
||||
}
|
||||
}).parse();
|
||||
} catch (final Resources.NotFoundException e) {
|
||||
Log.w(MmsService.TAG, "Can not get apns.xml " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String trimWithNullCheck(final String value) {
|
||||
return value != null ? value.trim() : null;
|
||||
}
|
||||
+25
-31
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2024 The LineageOS Project
|
||||
* Copyright (C) 2024-2025 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,6 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.v7.mms.CarrierConfigValuesLoader;
|
||||
import android.support.v7.mms.MmsManager;
|
||||
import android.telephony.CarrierConfigManager;
|
||||
@@ -33,18 +32,17 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.receiver.SmsReceiver;
|
||||
import com.android.messaging.sms.ApnDatabase;
|
||||
import com.android.messaging.sms.BugleApnSettingsLoader;
|
||||
import com.android.messaging.sms.BugleUserAgentInfoLoader;
|
||||
import com.android.messaging.sms.MmsConfig;
|
||||
import com.android.messaging.ui.ConversationDrawables;
|
||||
import com.android.messaging.ui.home.HomeAppLock;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.util.BuglePrefsKeys;
|
||||
import com.android.messaging.util.DebugUtils;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.Trace;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
/**
|
||||
@@ -61,6 +59,8 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
||||
super.onCreate();
|
||||
|
||||
FactoryImpl.register(getApplicationContext(), this);
|
||||
HomeAppLock.install(this);
|
||||
HomeToast.install(this);
|
||||
|
||||
sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||
@@ -84,14 +84,10 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
||||
final CarrierConfigValuesLoader carrierConfigValuesLoader =
|
||||
factory.getCarrierConfigValuesLoader();
|
||||
|
||||
maybeStartProfiling();
|
||||
|
||||
BugleApplication.updateAppConfig(context);
|
||||
|
||||
// Initialize MMS lib
|
||||
initMmsLib(context, carrierConfigValuesLoader);
|
||||
// Initialize APN database
|
||||
ApnDatabase.initializeAppContext(context);
|
||||
// Fixup messages in flight if we crashed and send any pending
|
||||
dataModel.onApplicationCreated();
|
||||
// Register carrier config change receiver
|
||||
@@ -132,6 +128,18 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
||||
Trace.endSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(final int level) {
|
||||
super.onTrimMemory(level);
|
||||
if (level < TRIM_MEMORY_UI_HIDDEN) {
|
||||
return;
|
||||
}
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "BugleApplication.onTrimMemory " + level);
|
||||
}
|
||||
reclaimCachedBitmaps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
@@ -139,7 +147,14 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "BugleApplication.onLowMemory");
|
||||
}
|
||||
Factory.get().reclaimMemory();
|
||||
reclaimCachedBitmaps();
|
||||
}
|
||||
|
||||
private static void reclaimCachedBitmaps() {
|
||||
final Factory factory = Factory.get();
|
||||
if (factory != null) {
|
||||
factory.reclaimMemory();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,27 +170,6 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeStartProfiling() {
|
||||
// App startup profiling support. To use it:
|
||||
// adb shell setprop log.tag.BugleProfile DEBUG
|
||||
// # Start the app, wait for a 30s, download trace file:
|
||||
// adb pull /data/data/com.android.messaging/cache/startup.trace /tmp
|
||||
// # Open trace file (using adt/tools/traceview)
|
||||
if (android.util.Log.isLoggable(LogUtil.PROFILE_TAG, android.util.Log.DEBUG)) {
|
||||
// Start method tracing with a big enough buffer and let it run for 30s.
|
||||
// Note we use a logging tag as we don't want to wait for gservices to start up.
|
||||
final File file = DebugUtils.getDebugFile("startup.trace", true);
|
||||
android.os.Debug.startMethodTracing(file.getAbsolutePath(), 160 * 1024 * 1024);
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
android.os.Debug.stopMethodTracing();
|
||||
// Allow world to see trace file
|
||||
DebugUtils.ensureReadable(file);
|
||||
LogUtil.d(LogUtil.PROFILE_TAG, "Tracing complete - "
|
||||
+ file.getAbsolutePath());
|
||||
}, 30000);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeHandleSharedPrefsUpgrade(final Factory factory) {
|
||||
final int existingVersion = factory.getApplicationPrefs().getInt(
|
||||
BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
|
||||
-2
@@ -66,6 +66,4 @@ public abstract class Factory {
|
||||
public abstract BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader();
|
||||
// Note this needs to run from any thread
|
||||
public abstract void reclaimMemory();
|
||||
|
||||
public abstract void onActivityResume();
|
||||
}
|
||||
-4
@@ -199,10 +199,6 @@ class FactoryImpl extends Factory {
|
||||
mMemoryCacheManager.reclaimMemory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResume() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaUtil getMediaUtil() {
|
||||
return mMediaUtil;
|
||||
+25
-1
@@ -533,6 +533,7 @@ public class BugleDatabaseOperations {
|
||||
// the sender is blocked, or we have been told to keep it archived.
|
||||
if (!keepArchived) {
|
||||
values.put(ConversationColumns.ARCHIVE_STATUS, 0);
|
||||
values.put(ConversationColumns.TRASH_TIMESTAMP, 0);
|
||||
}
|
||||
|
||||
final MessageData message = readMessage(dbWrapper, messageId);
|
||||
@@ -575,6 +576,20 @@ public class BugleDatabaseOperations {
|
||||
updateConversationRowIfExists(dbWrapper, conversationId, values);
|
||||
}
|
||||
|
||||
@DoesNotRunOnMainThread
|
||||
public static void updateConversationTrashTimestampInTransaction(
|
||||
final DatabaseWrapper dbWrapper, final String conversationId,
|
||||
final long trashTimestamp) {
|
||||
Assert.isNotMainThread();
|
||||
Assert.isTrue(dbWrapper.getDatabase().inTransaction());
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(ConversationColumns.TRASH_TIMESTAMP, trashTimestamp);
|
||||
if (trashTimestamp > 0) {
|
||||
values.put(ConversationColumns.ARCHIVE_STATUS, 0);
|
||||
}
|
||||
updateConversationRowIfExists(dbWrapper, conversationId, values);
|
||||
}
|
||||
|
||||
static void addSnippetTextAndPreviewToContentValues(final MessageData message,
|
||||
final boolean showDraft, final ContentValues values) {
|
||||
values.put(ConversationColumns.SHOW_DRAFT, showDraft ? 1 : 0);
|
||||
@@ -685,7 +700,16 @@ public class BugleDatabaseOperations {
|
||||
sortTimestamp = cursor.getLong(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (sortTimestamp == 0) {
|
||||
try (Cursor existing = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
|
||||
new String[]{ ConversationColumns.SORT_TIMESTAMP },
|
||||
ConversationColumns._ID + "=?",
|
||||
new String[]{ conversationId }, null, null, null)) {
|
||||
if (existing.moveToFirst()) {
|
||||
sortTimestamp = existing.getLong(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final ContentValues values = new ContentValues();
|
||||
if (draftMessage == null || !draftMessage.hasContent()) {
|
||||
+33
-18
@@ -66,6 +66,7 @@ import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
|
||||
import com.android.messaging.sms.MmsSmsUtils;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.home.MutedConversationStore;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
import com.android.messaging.util.BugleGservicesKeys;
|
||||
@@ -354,6 +355,9 @@ public class BugleNotifications {
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
// TODO: Need to fix this for multi conversation notifications to rate limit dings.
|
||||
final String conversationId = state.mConversationIds.first();
|
||||
if (MutedConversationStore.isMuted(context, conversationId)) {
|
||||
return;
|
||||
}
|
||||
String id = NotificationsUtil.DEFAULT_CHANNEL_ID;
|
||||
if (NotificationsUtil.getNotificationChannel(context, conversationId) != null) {
|
||||
id = conversationId;
|
||||
@@ -535,7 +539,10 @@ public class BugleNotifications {
|
||||
private static void createMessageNotification(final boolean silent,
|
||||
final String conversationId) {
|
||||
final NotificationState state = MessageNotificationState.getNotificationState();
|
||||
final boolean softSound = DataModel.get().isNewMessageObservable(conversationId);
|
||||
final boolean muted = MutedConversationStore.isMuted(
|
||||
Factory.get().getApplicationContext(), conversationId);
|
||||
final boolean softSound = !muted
|
||||
&& DataModel.get().isNewMessageObservable(conversationId);
|
||||
if (state == null) {
|
||||
cancel(PendingIntentConstants.SMS_NOTIFICATION_ID);
|
||||
if (softSound && !TextUtils.isEmpty(conversationId)) {
|
||||
@@ -718,7 +725,9 @@ public class BugleNotifications {
|
||||
setWearableGroupOptions(notifBuilder, notificationState);
|
||||
|
||||
if (notificationState instanceof MultiMessageNotificationState) {
|
||||
if (attachmentBitmap != null) {
|
||||
if (attachmentBitmap != null
|
||||
&& notificationState.mNotificationStyle
|
||||
instanceof NotificationCompat.BigPictureStyle) {
|
||||
// When we've got a picture attachment, we do some switcheroo trickery. When
|
||||
// the notification is expanded, we show the picture as a bigPicture. The small
|
||||
// icon shows the sender's avatar. When that same notification is collapsed, the
|
||||
@@ -783,28 +792,34 @@ public class BugleNotifications {
|
||||
final int requestCode = multiMessageNotificationState.getReplyIntentRequestCode();
|
||||
final PendingIntent replyPendingIntent = UIIntents.get()
|
||||
.getPendingIntentForSendingMessageToConversation(context,
|
||||
conversationId, selfId, requiresMms, requestCode);
|
||||
conversationId, selfId, requiresMms,
|
||||
multiMessageNotificationState.mTitle, requestCode);
|
||||
|
||||
final int replyLabelRes = requiresMms ? R.string.notification_reply_via_mms :
|
||||
R.string.notification_reply_via_sms;
|
||||
|
||||
final NotificationCompat.Action.Builder actionBuilder =
|
||||
// Phone: open the Home bottom-sheet composer. Wear keeps RemoteInput.
|
||||
final NotificationCompat.Action replyAction =
|
||||
new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
||||
context.getString(replyLabelRes), replyPendingIntent);
|
||||
final RemoteInput.Builder remoteInputBuilder = new RemoteInput.Builder(Intent.EXTRA_TEXT);
|
||||
remoteInputBuilder.setLabel(context.getString(R.string.notification_reply_prompt));
|
||||
actionBuilder.addRemoteInput(remoteInputBuilder.build());
|
||||
notifBuilder.addAction(actionBuilder.build());
|
||||
context.getString(R.string.home_notification_reply), replyPendingIntent)
|
||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||
.setShowsUserInterface(true)
|
||||
.build();
|
||||
notifBuilder.addAction(replyAction);
|
||||
|
||||
// Support the action on a wearable device
|
||||
final NotificationCompat.Action.Builder wearActionBuilder =
|
||||
new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
||||
context.getString(replyLabelRes), replyPendingIntent);
|
||||
final String[] choices = context.getResources().getStringArray(
|
||||
R.array.notification_reply_choices);
|
||||
remoteInputBuilder.setChoices(choices);
|
||||
wearActionBuilder.addRemoteInput(remoteInputBuilder.build());
|
||||
wearableExtender.addAction(wearActionBuilder.build());
|
||||
final RemoteInput wearRemoteInput = new RemoteInput.Builder(Intent.EXTRA_TEXT)
|
||||
.setLabel(context.getString(R.string.home_notification_reply_hint))
|
||||
.setAllowFreeFormInput(true)
|
||||
.setChoices(choices)
|
||||
.build();
|
||||
final int wearLabelRes = requiresMms ? R.string.notification_reply_via_mms :
|
||||
R.string.notification_reply_via_sms;
|
||||
wearableExtender.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
||||
context.getString(wearLabelRes), replyPendingIntent)
|
||||
.addRemoteInput(wearRemoteInput)
|
||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||
.setShowsUserInterface(false)
|
||||
.build());
|
||||
}
|
||||
|
||||
private static void addDownloadMmsAction(final NotificationCompat.Builder notifBuilder,
|
||||
+17
-8
@@ -28,6 +28,8 @@ import com.android.messaging.datamodel.action.ActionService;
|
||||
import com.android.messaging.datamodel.action.BackgroundWorker;
|
||||
import com.android.messaging.datamodel.data.BlockedParticipantsData;
|
||||
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
|
||||
import com.android.messaging.datamodel.data.ConversationSearchData;
|
||||
import com.android.messaging.datamodel.data.ConversationSearchData.ConversationSearchDataListener;
|
||||
import com.android.messaging.datamodel.data.ContactListItemData;
|
||||
import com.android.messaging.datamodel.data.ContactPickerData;
|
||||
import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
|
||||
@@ -42,9 +44,8 @@ import com.android.messaging.datamodel.data.MediaPickerData;
|
||||
import com.android.messaging.datamodel.data.MessagePartData;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
import com.android.messaging.datamodel.data.ParticipantListItemData;
|
||||
import com.android.messaging.datamodel.data.PeopleAndOptionsData;
|
||||
import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
|
||||
import com.android.messaging.datamodel.data.PeopleOptionsItemData;
|
||||
import com.android.messaging.datamodel.data.ConversationProfileData;
|
||||
import com.android.messaging.datamodel.data.ConversationProfileData.ConversationProfileDataListener;
|
||||
import com.android.messaging.datamodel.data.SettingsData;
|
||||
import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
|
||||
import com.android.messaging.datamodel.data.SubscriptionListData;
|
||||
@@ -67,8 +68,14 @@ public abstract class DataModel {
|
||||
get().getActionService().scheduleAction(action, code, delayMs);
|
||||
}
|
||||
|
||||
public ConversationListData createConversationListData(final Context context,
|
||||
final ConversationListDataListener listener, final boolean archivedMode) {
|
||||
return createConversationListData(context, listener, archivedMode, false);
|
||||
}
|
||||
|
||||
public abstract ConversationListData createConversationListData(final Context context,
|
||||
final ConversationListDataListener listener, final boolean archivedMode);
|
||||
final ConversationListDataListener listener, final boolean archivedMode,
|
||||
final boolean trashMode);
|
||||
|
||||
public abstract ConversationData createConversationData(final Context context,
|
||||
final ConversationDataListener listener, final String conversationId);
|
||||
@@ -83,10 +90,9 @@ public abstract class DataModel {
|
||||
public abstract LaunchConversationData createLaunchConversationData(
|
||||
LaunchConversationDataListener listener);
|
||||
|
||||
public abstract PeopleOptionsItemData createPeopleOptionsItemData(final Context context);
|
||||
|
||||
public abstract PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
|
||||
final Context context, final PeopleAndOptionsDataListener listener);
|
||||
public abstract ConversationProfileData createConversationProfileData(
|
||||
final String conversationId, final Context context,
|
||||
final ConversationProfileDataListener listener);
|
||||
|
||||
public abstract VCardContactItemData createVCardContactItemData(final Context context,
|
||||
final MessagePartData data);
|
||||
@@ -100,6 +106,9 @@ public abstract class DataModel {
|
||||
public abstract BlockedParticipantsData createBlockedParticipantsData(Context context,
|
||||
BlockedParticipantsDataListener listener);
|
||||
|
||||
public abstract ConversationSearchData createConversationSearchData(Context context,
|
||||
String conversationId, ConversationSearchDataListener listener);
|
||||
|
||||
public abstract SubscriptionListData createSubscriptonListData(Context context);
|
||||
|
||||
public abstract SettingsData createSettingsData(Context context, SettingsDataListener listener);
|
||||
+18
-13
@@ -26,8 +26,11 @@ import com.android.messaging.datamodel.action.ActionService;
|
||||
import com.android.messaging.datamodel.action.BackgroundWorker;
|
||||
import com.android.messaging.datamodel.action.FixupMessageStatusOnStartupAction;
|
||||
import com.android.messaging.datamodel.action.ProcessPendingMessagesAction;
|
||||
import com.android.messaging.datamodel.action.PurgeExpiredTrashAction;
|
||||
import com.android.messaging.datamodel.data.BlockedParticipantsData;
|
||||
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
|
||||
import com.android.messaging.datamodel.data.ConversationSearchData;
|
||||
import com.android.messaging.datamodel.data.ConversationSearchData.ConversationSearchDataListener;
|
||||
import com.android.messaging.datamodel.data.ContactListItemData;
|
||||
import com.android.messaging.datamodel.data.ContactPickerData;
|
||||
import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
|
||||
@@ -42,9 +45,8 @@ import com.android.messaging.datamodel.data.MediaPickerData;
|
||||
import com.android.messaging.datamodel.data.MessagePartData;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
import com.android.messaging.datamodel.data.ParticipantListItemData;
|
||||
import com.android.messaging.datamodel.data.PeopleAndOptionsData;
|
||||
import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
|
||||
import com.android.messaging.datamodel.data.PeopleOptionsItemData;
|
||||
import com.android.messaging.datamodel.data.ConversationProfileData;
|
||||
import com.android.messaging.datamodel.data.ConversationProfileData.ConversationProfileDataListener;
|
||||
import com.android.messaging.datamodel.data.SettingsData;
|
||||
import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
|
||||
import com.android.messaging.datamodel.data.SubscriptionListData;
|
||||
@@ -80,8 +82,9 @@ public class DataModelImpl extends DataModel {
|
||||
|
||||
@Override
|
||||
public ConversationListData createConversationListData(final Context context,
|
||||
final ConversationListDataListener listener, final boolean archivedMode) {
|
||||
return new ConversationListData(context, listener, archivedMode);
|
||||
final ConversationListDataListener listener, final boolean archivedMode,
|
||||
final boolean trashMode) {
|
||||
return new ConversationListData(context, listener, archivedMode, trashMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,6 +110,12 @@ public class DataModelImpl extends DataModel {
|
||||
return new BlockedParticipantsData(context, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConversationSearchData createConversationSearchData(final Context context,
|
||||
final String conversationId, final ConversationSearchDataListener listener) {
|
||||
return new ConversationSearchData(context, conversationId, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPickerData createMediaPickerData(final Context context) {
|
||||
return new MediaPickerData(context);
|
||||
@@ -119,14 +128,9 @@ public class DataModelImpl extends DataModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) {
|
||||
return new PeopleOptionsItemData(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
|
||||
final Context context, final PeopleAndOptionsDataListener listener) {
|
||||
return new PeopleAndOptionsData(conversationId, context, listener);
|
||||
public ConversationProfileData createConversationProfileData(final String conversationId,
|
||||
final Context context, final ConversationProfileDataListener listener) {
|
||||
return new ConversationProfileData(conversationId, context, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -209,6 +213,7 @@ public class DataModelImpl extends DataModel {
|
||||
createConnectivityUtilForEachActiveSubscription();
|
||||
|
||||
FixupMessageStatusOnStartupAction.fixupMessageStatus();
|
||||
PurgeExpiredTrashAction.purgeExpired();
|
||||
ProcessPendingMessagesAction.processFirstPendingMessage();
|
||||
SyncManager.immediateSync();
|
||||
|
||||
+10
@@ -101,6 +101,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
/* If this conversation is archived */
|
||||
public static final String ARCHIVE_STATUS = "archive_status";
|
||||
|
||||
/* When the conversation was moved to Trash; 0 if not in Trash */
|
||||
public static final String TRASH_TIMESTAMP = "trash_timestamp";
|
||||
|
||||
/* Timestamp for sorting purposes */
|
||||
public static final String SORT_TIMESTAMP = "sort_timestamp";
|
||||
|
||||
@@ -159,6 +162,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
+ ConversationColumns.DRAFT_PREVIEW_URI + " TEXT, "
|
||||
+ ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE + " TEXT, "
|
||||
+ ConversationColumns.ARCHIVE_STATUS + " INT DEFAULT(0), "
|
||||
+ ConversationColumns.TRASH_TIMESTAMP + " INT DEFAULT(0), "
|
||||
+ ConversationColumns.SORT_TIMESTAMP + " INT DEFAULT(0), "
|
||||
+ ConversationColumns.LAST_READ_TIMESTAMP + " INT DEFAULT(0), "
|
||||
+ ConversationColumns.ICON + " TEXT, "
|
||||
@@ -183,6 +187,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
+ " ON " + CONVERSATIONS_TABLE
|
||||
+ "(" + ConversationColumns.ARCHIVE_STATUS + ")";
|
||||
|
||||
private static final String CONVERSATIONS_TABLE_TRASH_TIMESTAMP_INDEX_SQL =
|
||||
"CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.TRASH_TIMESTAMP
|
||||
+ " ON " + CONVERSATIONS_TABLE
|
||||
+ "(" + ConversationColumns.TRASH_TIMESTAMP + ")";
|
||||
|
||||
private static final String CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL =
|
||||
"CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SORT_TIMESTAMP
|
||||
+ " ON " + CONVERSATIONS_TABLE
|
||||
@@ -525,6 +534,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final String[] CREATE_INDEX_SQLS = new String[] {
|
||||
CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL,
|
||||
CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL,
|
||||
CONVERSATIONS_TABLE_TRASH_TIMESTAMP_INDEX_SQL,
|
||||
CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL,
|
||||
MESSAGES_TABLE_SORT_INDEX_SQL,
|
||||
MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL,
|
||||
+14
@@ -48,6 +48,9 @@ public class DatabaseUpgradeHelper {
|
||||
if (currentVersion < 2) {
|
||||
currentVersion = upgradeToVersion2(db);
|
||||
}
|
||||
if (currentVersion < 3) {
|
||||
currentVersion = upgradeToVersion3(db);
|
||||
}
|
||||
// Rebuild all the views
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
DatabaseHelper.dropAllViews(db);
|
||||
@@ -63,6 +66,17 @@ public class DatabaseUpgradeHelper {
|
||||
return 2;
|
||||
}
|
||||
|
||||
private int upgradeToVersion3(final SQLiteDatabase db) {
|
||||
db.execSQL("ALTER TABLE " + DatabaseHelper.CONVERSATIONS_TABLE + " ADD COLUMN " +
|
||||
DatabaseHelper.ConversationColumns.TRASH_TIMESTAMP + " INT DEFAULT(0)");
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_" + DatabaseHelper.CONVERSATIONS_TABLE
|
||||
+ "_" + DatabaseHelper.ConversationColumns.TRASH_TIMESTAMP
|
||||
+ " ON " + DatabaseHelper.CONVERSATIONS_TABLE
|
||||
+ "(" + DatabaseHelper.ConversationColumns.TRASH_TIMESTAMP + ")");
|
||||
LogUtil.i(TAG, "Upgraded database to version 3");
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks db version correctness at the end of each milestone release. If target database
|
||||
* version lies beyond the version range that the current release may handle, we snap the
|
||||
+4
-1
@@ -103,7 +103,7 @@ public abstract class FileProvider extends ContentProvider {
|
||||
final String fileId = uri.getPath();
|
||||
if (isValidFileId(fileId)) {
|
||||
final File file = getFile(fileId, getExtensionFromUri(uri));
|
||||
return file.delete() ? 1 : 0;
|
||||
return file != null && file.delete() ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -114,6 +114,9 @@ public abstract class FileProvider extends ContentProvider {
|
||||
final String fileId = uri.getPath();
|
||||
if (isValidFileId(fileId)) {
|
||||
final File file = getFile(fileId, getExtensionFromUri(uri));
|
||||
if (file == null) {
|
||||
throw new FileNotFoundException(String.valueOf(uri));
|
||||
}
|
||||
final int mode =
|
||||
(TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
|
||||
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
|
||||
+2
@@ -120,6 +120,8 @@ public class FrequentContactsCursorBuilder {
|
||||
mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
|
||||
row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] =
|
||||
mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL);
|
||||
row[ContactUtil.INDEX_SORT_KEY] =
|
||||
mAllContactsCursor.getString(ContactUtil.INDEX_SORT_KEY);
|
||||
|
||||
boolean numberAlreadyAdded = false;
|
||||
for (Object[] oldRow : rows) {
|
||||
+8
-4
@@ -46,7 +46,7 @@ public class MediaScratchFileProvider extends FileProvider {
|
||||
private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap = new SimpleArrayMap<>();
|
||||
|
||||
public static final String AUTHORITY =
|
||||
"com.android.messaging.datamodel.MediaScratchFileProvider";
|
||||
"com.bnkode.notie.datamodel.MediaScratchFileProvider";
|
||||
private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace";
|
||||
|
||||
public static boolean isMediaScratchSpaceUri(final Uri uri) {
|
||||
@@ -68,8 +68,8 @@ public class MediaScratchFileProvider extends FileProvider {
|
||||
public static Uri buildMediaScratchSpaceUri(final String extension) {
|
||||
final Uri uri = FileProvider.buildFileUri(AUTHORITY, extension);
|
||||
final File file = getFileWithExtension(uri.getPath(), extension);
|
||||
if (!ensureFileExists(file)) {
|
||||
LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath());
|
||||
if (file == null || !ensureFileExists(file)) {
|
||||
LogUtil.e(TAG, "Failed to create temp file for " + uri);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
@@ -90,8 +90,12 @@ public class MediaScratchFileProvider extends FileProvider {
|
||||
|
||||
private static File getFileWithExtension(final String path, final String extension) {
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
String name = path;
|
||||
if (name != null && name.startsWith("/")) {
|
||||
name = name.substring(1);
|
||||
}
|
||||
final File filePath = new File(getDirectory(context),
|
||||
TextUtils.isEmpty(extension) ? path : path + "." + extension);
|
||||
TextUtils.isEmpty(extension) ? name : name + "." + extension);
|
||||
|
||||
try {
|
||||
if (!filePath.getCanonicalPath()
|
||||
+50
-88
@@ -39,6 +39,7 @@ import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationCompat.Builder;
|
||||
import androidx.core.app.NotificationCompat.WearableExtender;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.app.Person;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
@@ -50,6 +51,7 @@ import com.android.messaging.datamodel.data.MessagePartData;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.home.MutedConversationStore;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
import com.android.messaging.util.BugleGservicesKeys;
|
||||
@@ -234,14 +236,6 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
mParticipantCount = participantCount;
|
||||
}
|
||||
|
||||
public int getLatestMessageNotificationType() {
|
||||
final MessageLineInfo messageLineInfo = getLatestMessageLineInfo();
|
||||
if (messageLineInfo == null) {
|
||||
return BugleNotifications.LOCAL_SMS_NOTIFICATION;
|
||||
}
|
||||
return messageLineInfo.mNotificationType;
|
||||
}
|
||||
|
||||
public String getLatestMessageId() {
|
||||
final MessageLineInfo messageLineInfo = getLatestMessageLineInfo();
|
||||
if (messageLineInfo == null) {
|
||||
@@ -362,7 +356,6 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
if (!(convInfo.mLineInfos.get(0) instanceof MessageLineInfo)) {
|
||||
continue;
|
||||
}
|
||||
setPeopleForConversation(convInfo.mConversationId);
|
||||
final ConversationInfoList list = new ConversationInfoList(
|
||||
convInfo.mTotalMessageCount, Lists.newArrayList(convInfo));
|
||||
mChildren.add(new BundledMessageNotificationState(list, i));
|
||||
@@ -452,8 +445,6 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
super(convList);
|
||||
// This conversation has been accepted.
|
||||
final ConversationLineInfo convInfo = convList.mConvInfos.get(0);
|
||||
setAvatarUrlsForConversation(convInfo.mConversationId);
|
||||
setPeopleForConversation(convInfo.mConversationId);
|
||||
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
MessageLineInfo messageInfo = (MessageLineInfo) convInfo.mLineInfos.get(0);
|
||||
@@ -476,7 +467,7 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
final String attachment = context.getString(message);
|
||||
final SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
|
||||
if (!TextUtils.isEmpty(mContent)) {
|
||||
spanBuilder.append(mContent).append(System.getProperty("line.separator"));
|
||||
spanBuilder.append(mContent).append(System.lineSeparator());
|
||||
}
|
||||
final int start = spanBuilder.length();
|
||||
spanBuilder.append(attachment);
|
||||
@@ -505,85 +496,62 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
@Override
|
||||
protected NotificationCompat.Style build(final Builder builder) {
|
||||
builder.setContentTitle(mTitle)
|
||||
.setTicker(getTicker());
|
||||
.setTicker(getTicker())
|
||||
.setContentText(mContent);
|
||||
|
||||
NotificationCompat.Style notifStyle;
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
|
||||
final List<NotificationLineInfo> lineInfos = convInfo.mLineInfos;
|
||||
final int messageCount = lineInfos.size();
|
||||
// At this point, all the messages come from the same conversation. We need to load
|
||||
// the sender's avatar and then finish building the notification on a callback.
|
||||
|
||||
builder.setContentText(mContent); // for collapsed state
|
||||
|
||||
if (messageCount == 1) {
|
||||
final boolean shouldShowImage = ContentType.isImageType(mAttachmentType)
|
||||
|| (ContentType.isVideoType(mAttachmentType));
|
||||
if (mAttachmentUri != null && shouldShowImage) {
|
||||
// Show "Picture" as the content
|
||||
final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfos.get(0);
|
||||
String authorFirstName = messageLineInfo.mAuthorFirstName;
|
||||
|
||||
// For the collapsed state, just show "picture" unless this is a
|
||||
// group conversation. If it's a group, show the sender name and
|
||||
// "picture".
|
||||
final CharSequence tickerTag =
|
||||
BugleNotifications.formatAttachmentTag(authorFirstName,
|
||||
mAttachmentType);
|
||||
// For 1:1 notifications don't show first name in the notification, but
|
||||
// do show it in the ticker text
|
||||
CharSequence pictureTag = tickerTag;
|
||||
if (!convInfo.mIsGroup) {
|
||||
authorFirstName = null;
|
||||
pictureTag = BugleNotifications.formatAttachmentTag(authorFirstName,
|
||||
mAttachmentType);
|
||||
final Person user = new Person.Builder()
|
||||
.setName(context.getString(R.string.home_notification_you))
|
||||
.build();
|
||||
final NotificationCompat.MessagingStyle style =
|
||||
new NotificationCompat.MessagingStyle(user);
|
||||
if (convInfo.mIsGroup) {
|
||||
style.setGroupConversation(true);
|
||||
if (!TextUtils.isEmpty(convInfo.mGroupConversationName)) {
|
||||
style.setConversationTitle(convInfo.mGroupConversationName);
|
||||
}
|
||||
builder.setContentText(pictureTag);
|
||||
builder.setTicker(tickerTag);
|
||||
|
||||
notifStyle = new NotificationCompat.BigPictureStyle(builder)
|
||||
.setSummaryText(BugleNotifications.formatInboxMessage(
|
||||
authorFirstName,
|
||||
null, null,
|
||||
null)); // expanded state, just show sender
|
||||
} else {
|
||||
notifStyle = new NotificationCompat.BigTextStyle(builder)
|
||||
.bigText(mContent);
|
||||
}
|
||||
} else {
|
||||
// We've got multiple messages for the same sender.
|
||||
// Starting with the oldest new message, display the full text of each message.
|
||||
// Begin a line for each subsequent message.
|
||||
final SpannableStringBuilder buf = new SpannableStringBuilder();
|
||||
|
||||
// MessagingStyle wants chronological order (oldest first). Latest is at index 0.
|
||||
for (int i = lineInfos.size() - 1; i >= 0; --i) {
|
||||
final NotificationLineInfo info = lineInfos.get(i);
|
||||
if (!(info instanceof MessageLineInfo)) {
|
||||
continue;
|
||||
}
|
||||
final MessageLineInfo messageLineInfo = (MessageLineInfo) info;
|
||||
mAttachmentUri = messageLineInfo.mAttachmentUri;
|
||||
mAttachmentType = messageLineInfo.mAttachmentType;
|
||||
CharSequence text = messageLineInfo.mText;
|
||||
if (!TextUtils.isEmpty(text) || mAttachmentUri != null) {
|
||||
if (convInfo.mIsGroup) {
|
||||
// append the bold name to the front of the message
|
||||
text = BugleNotifications.buildSpaceSeparatedMessage(
|
||||
messageLineInfo.mAuthorFullName, text, mAttachmentUri,
|
||||
mAttachmentType);
|
||||
} else {
|
||||
text = BugleNotifications.buildSpaceSeparatedMessage(
|
||||
null, text, mAttachmentUri, mAttachmentType);
|
||||
if (TextUtils.isEmpty(text) && messageLineInfo.mAttachmentUri != null) {
|
||||
text = BugleNotifications.formatAttachmentTag(
|
||||
convInfo.mIsGroup ? messageLineInfo.mAuthorFirstName : null,
|
||||
messageLineInfo.mAttachmentType);
|
||||
}
|
||||
buf.append(text);
|
||||
if (i > 0) {
|
||||
buf.append('\n');
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
continue;
|
||||
}
|
||||
final Person.Builder personBuilder = new Person.Builder()
|
||||
.setName(!TextUtils.isEmpty(messageLineInfo.mAuthorFullName)
|
||||
? messageLineInfo.mAuthorFullName : mTitle);
|
||||
if (convInfo.mContactUri != null) {
|
||||
personBuilder.setUri(convInfo.mContactUri.toString());
|
||||
}
|
||||
final long timestamp = Math.max(0, convInfo.mReceivedTimestamp - i);
|
||||
final NotificationCompat.MessagingStyle.Message message =
|
||||
new NotificationCompat.MessagingStyle.Message(
|
||||
text, timestamp, personBuilder.build());
|
||||
if (messageLineInfo.mAttachmentUri != null
|
||||
&& !TextUtils.isEmpty(messageLineInfo.mAttachmentType)
|
||||
&& (ContentType.isImageType(messageLineInfo.mAttachmentType)
|
||||
|| ContentType.isVideoType(messageLineInfo.mAttachmentType))) {
|
||||
message.setData(messageLineInfo.mAttachmentType,
|
||||
messageLineInfo.mAttachmentUri);
|
||||
}
|
||||
style.addMessage(message);
|
||||
}
|
||||
|
||||
// Show a single notification -- big style with the text of all the messages
|
||||
notifStyle = new NotificationCompat.BigTextStyle(builder).bigText(buf);
|
||||
}
|
||||
builder.setWhen(convInfo.mReceivedTimestamp);
|
||||
return notifStyle;
|
||||
return style;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -835,6 +803,9 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
final String messageText = convMessageData.getText();
|
||||
|
||||
final String convId = convMessageData.getConversationId();
|
||||
if (MutedConversationStore.isMuted(context, convId)) {
|
||||
continue;
|
||||
}
|
||||
final String messageId = convMessageData.getMessageId();
|
||||
|
||||
CharSequence text = messageText;
|
||||
@@ -851,6 +822,9 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
if (currConvInfo == null) {
|
||||
final ConversationListItemData convData =
|
||||
ConversationListItemData.getExistingConversation(db, convId);
|
||||
if (convData == null || convData.getIsTrashed()) {
|
||||
continue;
|
||||
}
|
||||
final int subId = BugleDatabaseOperations.getSelfSubscriptionId(db,
|
||||
convData.getSelfId());
|
||||
groupConversationName = convData.getName();
|
||||
@@ -917,7 +891,7 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
// Now add the actual message text below the subject header.
|
||||
spanBuilder.append(System.getProperty("line.separator") + text);
|
||||
spanBuilder.append(System.lineSeparator() + text);
|
||||
}
|
||||
text = spanBuilder;
|
||||
}
|
||||
@@ -1059,18 +1033,6 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLatestMessageNotificationType() {
|
||||
// This function is called to determine whether the most recent notification applies
|
||||
// to an sms conversation or a hangout conversation. We have different ringtone/vibrate
|
||||
// settings for both types of conversations.
|
||||
if (mConvList.mConvInfos.size() > 0) {
|
||||
final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
|
||||
return convInfo.getLatestMessageNotificationType();
|
||||
}
|
||||
return BugleNotifications.LOCAL_SMS_NOTIFICATION;
|
||||
}
|
||||
|
||||
protected CharSequence getTicker() {
|
||||
return BugleNotifications.buildColonSeparatedMessage(
|
||||
mTickerSender != null ? mTickerSender : mTitle,
|
||||
+51
-1
@@ -53,7 +53,7 @@ public class MessagingContentProvider extends ContentProvider {
|
||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||
|
||||
public static final String AUTHORITY =
|
||||
"com.android.messaging.datamodel.MessagingContentProvider";
|
||||
"com.bnkode.notie.datamodel.MessagingContentProvider";
|
||||
private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/';
|
||||
|
||||
// Conversations query
|
||||
@@ -70,6 +70,11 @@ public class MessagingContentProvider extends ContentProvider {
|
||||
public static final Uri CONVERSATION_MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY +
|
||||
MESSAGES_QUERY + "/conversation");
|
||||
|
||||
public static final Uri CONVERSATION_MESSAGES_SEARCH_URI = Uri.parse(CONTENT_AUTHORITY +
|
||||
MESSAGES_QUERY + "/search");
|
||||
|
||||
private static final String SEARCH_QUERY_PARAMETER = "q";
|
||||
|
||||
// Conversation participants query
|
||||
private static final String PARTICIPANTS_QUERY = "participants";
|
||||
|
||||
@@ -146,6 +151,7 @@ public class MessagingContentProvider extends ContentProvider {
|
||||
|
||||
private static final int CONVERSATION_QUERY_CODE = 20;
|
||||
private static final int CONVERSATION_MESSAGES_QUERY_CODE = 30;
|
||||
private static final int CONVERSATION_MESSAGES_SEARCH_QUERY_CODE = 35;
|
||||
private static final int CONVERSATION_PARTICIPANTS_QUERY_CODE = 40;
|
||||
private static final int CONVERSATION_IMAGES_QUERY_CODE = 50;
|
||||
private static final int DRAFT_IMAGES_QUERY_CODE = 60;
|
||||
@@ -158,6 +164,8 @@ public class MessagingContentProvider extends ContentProvider {
|
||||
sURIMatcher.addURI(AUTHORITY, CONVERSATIONS_QUERY + "/*", CONVERSATION_QUERY_CODE);
|
||||
sURIMatcher.addURI(AUTHORITY, MESSAGES_QUERY + "/conversation/*",
|
||||
CONVERSATION_MESSAGES_QUERY_CODE);
|
||||
sURIMatcher.addURI(AUTHORITY, MESSAGES_QUERY + "/search/*",
|
||||
CONVERSATION_MESSAGES_SEARCH_QUERY_CODE);
|
||||
sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY + "/conversation/*",
|
||||
CONVERSATION_PARTICIPANTS_QUERY_CODE);
|
||||
sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY, PARTICIPANTS_QUERY_CODE);
|
||||
@@ -176,6 +184,17 @@ public class MessagingContentProvider extends ContentProvider {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a uri for the messages of a conversation whose text matches {@code searchText}.
|
||||
*/
|
||||
public static Uri buildConversationMessagesSearchUri(final String conversationId,
|
||||
final String searchText) {
|
||||
return CONVERSATION_MESSAGES_SEARCH_URI.buildUpon()
|
||||
.appendPath(conversationId)
|
||||
.appendQueryParameter(SEARCH_QUERY_PARAMETER, searchText)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static void notifyMessagesChanged(final String conversationId) {
|
||||
final Uri uri = buildConversationMessagesUri(conversationId);
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
@@ -319,6 +338,19 @@ public class MessagingContentProvider extends ContentProvider {
|
||||
} else {
|
||||
throw new IllegalArgumentException("Malformed URI " + uri);
|
||||
}
|
||||
case CONVERSATION_MESSAGES_SEARCH_QUERY_CODE:
|
||||
if (uri.getPathSegments().size() == 3) {
|
||||
// Like the query above this is a raw query, so selection/sorting is invalid.
|
||||
if (selection == null && selectionArgs == null && sortOrder == null) {
|
||||
return queryConversationMessagesSearch(uri.getPathSegments().get(2),
|
||||
uri.getQueryParameter(SEARCH_QUERY_PARAMETER));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot set selection or sort order with this query");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Malformed URI " + uri);
|
||||
}
|
||||
case CONVERSATION_IMAGES_QUERY_CODE:
|
||||
queryBuilder.setTables(ConversationImagePartsView.getViewName());
|
||||
if (uri.getPathSegments().size() == 2) {
|
||||
@@ -366,6 +398,24 @@ public class MessagingContentProvider extends ContentProvider {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
private Cursor queryConversationMessagesSearch(final String conversationId,
|
||||
final String searchText) {
|
||||
final String[] queryArgs = { conversationId, "%" + escapeLikePattern(searchText) + "%" };
|
||||
final Cursor cursor = getDatabaseWrapper().rawQuery(
|
||||
ConversationMessageData.getConversationMessagesSearchQuerySql(), queryArgs);
|
||||
// Follow the plain message uri so that incoming messages refresh the results as well.
|
||||
cursor.setNotificationUri(getContext().getContentResolver(),
|
||||
buildConversationMessagesUri(conversationId));
|
||||
return cursor;
|
||||
}
|
||||
|
||||
private static String escapeLikePattern(final String text) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return "";
|
||||
}
|
||||
return text.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull final Uri uri) {
|
||||
final StringBuilder sb = new
|
||||
+1
-1
@@ -32,7 +32,7 @@ import java.io.IOException;
|
||||
public class MmsFileProvider extends FileProvider {
|
||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||
|
||||
static final String AUTHORITY = "com.android.messaging.datamodel.MmsFileProvider";
|
||||
static final String AUTHORITY = "com.bnkode.notie.datamodel.MmsFileProvider";
|
||||
private static final String RAW_MMS_DIR = "rawmms";
|
||||
|
||||
/**
|
||||
-18
@@ -94,12 +94,6 @@ public abstract class NotificationState {
|
||||
*/
|
||||
protected abstract NotificationCompat.Style build(NotificationCompat.Builder builder);
|
||||
|
||||
protected void setAvatarUrlsForConversation(final String conversationId) {
|
||||
}
|
||||
|
||||
protected void setPeopleForConversation(final String conversationId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves request codes for this notification type. By default 2 codes are reserved, one for
|
||||
* the main intent and another for the cancel intent. Override this function to reserve more.
|
||||
@@ -108,10 +102,6 @@ public abstract class NotificationState {
|
||||
return NUM_REQUEST_CODES_NEEDED;
|
||||
}
|
||||
|
||||
public int getContentIntentRequestCode() {
|
||||
return mBaseRequestCode + CONTENT_INTENT_REQUEST_CODE_OFFSET;
|
||||
}
|
||||
|
||||
public int getClearIntentRequestCode() {
|
||||
return mBaseRequestCode + CLEAR_INTENT_REQUEST_CODE_OFFSET;
|
||||
}
|
||||
@@ -121,14 +111,6 @@ public abstract class NotificationState {
|
||||
*/
|
||||
public abstract int getIcon();
|
||||
|
||||
/**
|
||||
* @return the type of notification that should be used from {@link RealTimeChatNotifications}
|
||||
* so that the proper ringtone and vibrate settings can be used.
|
||||
*/
|
||||
public int getLatestMessageNotificationType() {
|
||||
return BugleNotifications.LOCAL_SMS_NOTIFICATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the notification priority level for this notification.
|
||||
*/
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.android.messaging.datamodel;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.datamodel.action.InsertNewMessageAction;
|
||||
import com.android.messaging.datamodel.data.MessageData;
|
||||
import com.android.messaging.receiver.ScheduledMessageReceiver;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Persists compose-time scheduled sends and arms AlarmManager to deliver them. */
|
||||
public final class ScheduledMessageStore {
|
||||
private static final String PREFS = "home_scheduled_messages";
|
||||
private static final String KEY_ENTRIES = "entries";
|
||||
private static final String JSON_ID = "id";
|
||||
private static final String JSON_SEND_AT = "sendAt";
|
||||
private static final String JSON_PAYLOAD = "payload";
|
||||
|
||||
private ScheduledMessageStore() {
|
||||
}
|
||||
|
||||
public static void schedule(@NonNull final Context context, @NonNull final MessageData message,
|
||||
final long sendAt) {
|
||||
final Entry entry = new Entry();
|
||||
entry.id = UUID.randomUUID().toString();
|
||||
entry.sendAt = sendAt;
|
||||
entry.payload = encode(message);
|
||||
if (TextUtils.isEmpty(entry.payload)) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Scheduled message encode failed");
|
||||
return;
|
||||
}
|
||||
final List<Entry> entries = loadLocked(context);
|
||||
entries.add(entry);
|
||||
saveLocked(context, entries);
|
||||
setAlarm(context, entry);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void sendNow(@NonNull final Context context, @Nullable final String id) {
|
||||
if (TextUtils.isEmpty(id)) {
|
||||
return;
|
||||
}
|
||||
Entry match = null;
|
||||
synchronized (ScheduledMessageStore.class) {
|
||||
final List<Entry> entries = loadLocked(context);
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (id.equals(entries.get(i).id)) {
|
||||
match = entries.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match != null) {
|
||||
saveLocked(context, entries);
|
||||
}
|
||||
}
|
||||
if (match == null) {
|
||||
return;
|
||||
}
|
||||
cancelAlarm(context, match.id);
|
||||
final MessageData message = decode(match.payload);
|
||||
if (message == null || !message.hasContent()) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Scheduled message payload missing for " + id);
|
||||
return;
|
||||
}
|
||||
InsertNewMessageAction.insertNewMessage(message);
|
||||
}
|
||||
|
||||
public static void rescheduleAll(@NonNull final Context context) {
|
||||
final List<Entry> entries = loadLocked(context);
|
||||
final long now = System.currentTimeMillis();
|
||||
final List<Entry> due = new ArrayList<>();
|
||||
synchronized (ScheduledMessageStore.class) {
|
||||
final List<Entry> remaining = new ArrayList<>();
|
||||
for (final Entry entry : entries) {
|
||||
if (entry.sendAt <= now) {
|
||||
due.add(entry);
|
||||
} else {
|
||||
remaining.add(entry);
|
||||
setAlarm(context, entry);
|
||||
}
|
||||
}
|
||||
if (due.size() != entries.size()) {
|
||||
saveLocked(context, remaining);
|
||||
} else if (!due.isEmpty()) {
|
||||
saveLocked(context, new ArrayList<>());
|
||||
}
|
||||
}
|
||||
for (final Entry entry : due) {
|
||||
final MessageData message = decode(entry.payload);
|
||||
if (message != null && message.hasContent()) {
|
||||
InsertNewMessageAction.insertNewMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void setAlarm(@NonNull final Context context, @NonNull final Entry entry) {
|
||||
final AlarmManager alarms = context.getSystemService(AlarmManager.class);
|
||||
if (alarms == null) {
|
||||
return;
|
||||
}
|
||||
final PendingIntent pending = pendingIntent(context, entry.id, true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarms.canScheduleExactAlarms()) {
|
||||
alarms.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, entry.sendAt, pending);
|
||||
return;
|
||||
}
|
||||
alarms.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, entry.sendAt, pending);
|
||||
}
|
||||
|
||||
private static void cancelAlarm(@NonNull final Context context, @NonNull final String id) {
|
||||
final AlarmManager alarms = context.getSystemService(AlarmManager.class);
|
||||
if (alarms == null) {
|
||||
return;
|
||||
}
|
||||
final PendingIntent pending = pendingIntent(context, id, false);
|
||||
if (pending != null) {
|
||||
alarms.cancel(pending);
|
||||
}
|
||||
}
|
||||
|
||||
private static PendingIntent pendingIntent(@NonNull final Context context,
|
||||
@NonNull final String id, final boolean update) {
|
||||
final Intent intent = new Intent(context, ScheduledMessageReceiver.class);
|
||||
intent.setAction(ScheduledMessageReceiver.ACTION_SEND);
|
||||
intent.setData(Uri.parse("msgq-schedule:" + id));
|
||||
intent.putExtra(ScheduledMessageReceiver.EXTRA_ID, id);
|
||||
final int flags = PendingIntent.FLAG_IMMUTABLE
|
||||
| (update ? PendingIntent.FLAG_UPDATE_CURRENT : PendingIntent.FLAG_NO_CREATE);
|
||||
return PendingIntent.getBroadcast(context.getApplicationContext(), id.hashCode(),
|
||||
intent, flags);
|
||||
}
|
||||
|
||||
private static synchronized List<Entry> loadLocked(@NonNull final Context context) {
|
||||
final SharedPreferences prefs = prefs(context);
|
||||
final String raw = prefs.getString(KEY_ENTRIES, "[]");
|
||||
final List<Entry> entries = new ArrayList<>();
|
||||
try {
|
||||
final JSONArray array = new JSONArray(raw);
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
final JSONObject object = array.getJSONObject(i);
|
||||
final Entry entry = new Entry();
|
||||
entry.id = object.optString(JSON_ID);
|
||||
entry.sendAt = object.optLong(JSON_SEND_AT);
|
||||
entry.payload = object.optString(JSON_PAYLOAD);
|
||||
if (!TextUtils.isEmpty(entry.id) && !TextUtils.isEmpty(entry.payload)) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
} catch (final Exception exception) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to load scheduled messages", exception);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static synchronized void saveLocked(@NonNull final Context context,
|
||||
@NonNull final List<Entry> entries) {
|
||||
final JSONArray array = new JSONArray();
|
||||
try {
|
||||
for (final Entry entry : entries) {
|
||||
final JSONObject object = new JSONObject();
|
||||
object.put(JSON_ID, entry.id);
|
||||
object.put(JSON_SEND_AT, entry.sendAt);
|
||||
object.put(JSON_PAYLOAD, entry.payload);
|
||||
array.put(object);
|
||||
}
|
||||
prefs(context).edit().putString(KEY_ENTRIES, array.toString()).apply();
|
||||
} catch (final Exception exception) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to save scheduled messages", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static SharedPreferences prefs(@NonNull final Context context) {
|
||||
final Context app = context.getApplicationContext() != null
|
||||
? context.getApplicationContext()
|
||||
: Factory.get().getApplicationContext();
|
||||
return app.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String encode(@NonNull final MessageData message) {
|
||||
final Parcel parcel = Parcel.obtain();
|
||||
try {
|
||||
message.writeToParcel(parcel, 0);
|
||||
return Base64.encodeToString(parcel.marshall(), Base64.NO_WRAP);
|
||||
} catch (final Exception exception) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to encode scheduled message", exception);
|
||||
return null;
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MessageData decode(@Nullable final String payload) {
|
||||
if (TextUtils.isEmpty(payload)) {
|
||||
return null;
|
||||
}
|
||||
final Parcel parcel = Parcel.obtain();
|
||||
try {
|
||||
final byte[] bytes = Base64.decode(payload, Base64.NO_WRAP);
|
||||
parcel.unmarshall(bytes, 0, bytes.length);
|
||||
parcel.setDataPosition(0);
|
||||
return MessageData.CREATOR.createFromParcel(parcel);
|
||||
} catch (final Exception exception) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to decode scheduled message", exception);
|
||||
return null;
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Entry {
|
||||
String id;
|
||||
long sendAt;
|
||||
String payload;
|
||||
}
|
||||
}
|
||||
+1
-7
@@ -137,7 +137,7 @@ public class ActionServiceImpl extends JobIntentService {
|
||||
* Broadcast receiver for alarms scheduled through ActionService.
|
||||
*/
|
||||
public static class PendingActionReceiver extends BroadcastReceiver {
|
||||
static final String ACTION = "com.android.messaging.datamodel.PENDING_ACTION";
|
||||
static final String ACTION = "com.bnkode.notie.datamodel.PENDING_ACTION";
|
||||
|
||||
/**
|
||||
* Allocate an intent with a specific opcode and alarm action.
|
||||
@@ -203,17 +203,11 @@ public class ActionServiceImpl extends JobIntentService {
|
||||
mBackgroundWorker = DataModel.get().getBackgroundWorkerForActionService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue intent to the ActionService.
|
||||
*/
|
||||
private static void startServiceWithIntent(final Intent intent) {
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
|
||||
intent.setClass(context, ActionServiceImpl.class);
|
||||
enqueueWork(context, intent);
|
||||
}
|
||||
+6
-6
@@ -22,6 +22,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.data.MessageData;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
@@ -115,13 +116,13 @@ public class BugleActionToasts {
|
||||
}
|
||||
|
||||
private static void showToast(final int messageResId) {
|
||||
ThreadUtil.getMainThreadHandler().post(() -> Toast.makeText(getApplicationContext(),
|
||||
getApplicationContext().getString(messageResId), Toast.LENGTH_LONG).show());
|
||||
ThreadUtil.getMainThreadHandler().post(() -> HomeToast.show(getApplicationContext(),
|
||||
messageResId, Toast.LENGTH_LONG));
|
||||
}
|
||||
|
||||
private static void showToast(final String message) {
|
||||
ThreadUtil.getMainThreadHandler().post(() ->
|
||||
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show());
|
||||
HomeToast.show(getApplicationContext(), message, Toast.LENGTH_LONG));
|
||||
}
|
||||
|
||||
private static Context getApplicationContext() {
|
||||
@@ -143,12 +144,11 @@ public class BugleActionToasts {
|
||||
final boolean block,
|
||||
final String destination) {
|
||||
if (success) {
|
||||
Toast.makeText(mContext,
|
||||
HomeToast.show(mContext,
|
||||
block
|
||||
? R.string.update_destination_blocked
|
||||
: R.string.update_destination_unblocked,
|
||||
Toast.LENGTH_LONG
|
||||
).show();
|
||||
Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -132,6 +132,13 @@ public class InsertNewMessageAction extends Action implements Parcelable {
|
||||
final DatabaseWrapper db = DataModel.get().getDatabase();
|
||||
final String conversationId = message.getConversationId();
|
||||
|
||||
final ConversationListItemData conversation =
|
||||
ConversationListItemData.getExistingConversation(db, conversationId);
|
||||
if (conversation != null && conversation.getIsTrashed()) {
|
||||
LogUtil.w(TAG, "InsertNewMessageAction: conversation is in trash");
|
||||
return null;
|
||||
}
|
||||
|
||||
final ParticipantData self = getSelf(db, conversationId, message);
|
||||
if (self == null) {
|
||||
return null;
|
||||
@@ -190,6 +197,7 @@ public class InsertNewMessageAction extends Action implements Parcelable {
|
||||
messageToSend, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT);
|
||||
}
|
||||
MessagingContentProvider.notifyConversationListChanged();
|
||||
MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
|
||||
ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
|
||||
|
||||
return message;
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2024-2025 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.action;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDoneException;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.messaging.datamodel.BugleDatabaseOperations;
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.DatabaseHelper;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.util.Assert;
|
||||
|
||||
/**
|
||||
* Marks the latest message in a conversation as unread (iOS-style thread unread).
|
||||
*/
|
||||
public class MarkAsUnreadAction extends Action implements Parcelable {
|
||||
private static final String KEY_CONVERSATION_ID = "conversation_id";
|
||||
|
||||
public static void markAsUnread(final String conversationId) {
|
||||
final MarkAsUnreadAction action = new MarkAsUnreadAction(conversationId);
|
||||
action.start();
|
||||
}
|
||||
|
||||
private MarkAsUnreadAction(final String conversationId) {
|
||||
actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object executeAction() {
|
||||
final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
|
||||
if (TextUtils.isEmpty(conversationId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final DatabaseWrapper db = DataModel.get().getDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
String latestMessageId;
|
||||
try {
|
||||
final SQLiteStatement latestMessageIdSql =
|
||||
BugleDatabaseOperations.getQueryMessagesLatestMessageStatement(
|
||||
db, conversationId);
|
||||
latestMessageId = latestMessageIdSql.simpleQueryForString();
|
||||
} catch (final SQLiteDoneException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(latestMessageId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(MessageColumns.READ, 0);
|
||||
values.put(MessageColumns.SEEN, 0);
|
||||
|
||||
final int count = db.update(DatabaseHelper.MESSAGES_TABLE, values,
|
||||
MessageColumns._ID + "=? AND (" + MessageColumns.READ + " != 0 OR "
|
||||
+ MessageColumns.SEEN + " != 0)",
|
||||
new String[] { latestMessageId });
|
||||
if (count > 0) {
|
||||
updateTelephonyReadStatus(db, latestMessageId);
|
||||
MessagingContentProvider.notifyMessagesChanged(conversationId);
|
||||
MessagingContentProvider.notifyConversationListChanged();
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void updateTelephonyReadStatus(final DatabaseWrapper db,
|
||||
final String messageId) {
|
||||
Assert.isNotMainThread();
|
||||
try (Cursor cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
|
||||
new String[] { MessageColumns.SMS_MESSAGE_URI },
|
||||
MessageColumns._ID + "=?",
|
||||
new String[] { messageId },
|
||||
null, null, null)) {
|
||||
if (!cursor.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
final String smsMessageUri = cursor.getString(0);
|
||||
if (TextUtils.isEmpty(smsMessageUri)) {
|
||||
return;
|
||||
}
|
||||
final ContentValues telephonyValues = new ContentValues();
|
||||
telephonyValues.put("read", 0);
|
||||
telephonyValues.put("seen", 0);
|
||||
Factory.get().getApplicationContext().getContentResolver().update(
|
||||
Uri.parse(smsMessageUri),
|
||||
telephonyValues,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
private MarkAsUnreadAction(final Parcel in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MarkAsUnreadAction> CREATOR =
|
||||
new Parcelable.Creator<>() {
|
||||
@Override
|
||||
public MarkAsUnreadAction createFromParcel(final Parcel in) {
|
||||
return new MarkAsUnreadAction(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarkAsUnreadAction[] newArray(final int size) {
|
||||
return new MarkAsUnreadAction[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull final Parcel parcel, final int flags) {
|
||||
writeActionToParcel(parcel, flags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.android.messaging.datamodel.action;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.DatabaseHelper;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** Permanently deletes conversations that have been in Trash longer than 30 days. */
|
||||
public class PurgeExpiredTrashAction extends Action {
|
||||
|
||||
private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
|
||||
|
||||
public static void purgeExpired() {
|
||||
new PurgeExpiredTrashAction().start();
|
||||
}
|
||||
|
||||
private PurgeExpiredTrashAction() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object executeAction() {
|
||||
final long cutoff = System.currentTimeMillis()
|
||||
- UpdateConversationTrashStatusAction.RETENTION_MS;
|
||||
final DatabaseWrapper db = DataModel.get().getDatabase();
|
||||
final ArrayList<String> ids = new ArrayList<>();
|
||||
final ArrayList<Long> timestamps = new ArrayList<>();
|
||||
try (Cursor cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
|
||||
new String[] { ConversationColumns._ID, ConversationColumns.SORT_TIMESTAMP },
|
||||
ConversationColumns.TRASH_TIMESTAMP + " > 0 AND "
|
||||
+ ConversationColumns.TRASH_TIMESTAMP + " < ?",
|
||||
new String[] { Long.toString(cutoff) },
|
||||
null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
ids.add(cursor.getString(0));
|
||||
timestamps.add(cursor.getLong(1));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
DeleteConversationAction.deleteConversation(ids.get(i), timestamps.get(i));
|
||||
}
|
||||
if (!ids.isEmpty()) {
|
||||
LogUtil.i(TAG, "Purged " + ids.size() + " expired trash conversations");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PurgeExpiredTrashAction(final Parcel in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<PurgeExpiredTrashAction> CREATOR
|
||||
= new Parcelable.Creator<>() {
|
||||
@Override
|
||||
public PurgeExpiredTrashAction createFromParcel(final Parcel in) {
|
||||
return new PurgeExpiredTrashAction(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PurgeExpiredTrashAction[] newArray(final int size) {
|
||||
return new PurgeExpiredTrashAction[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull final Parcel parcel, final int flags) {
|
||||
writeActionToParcel(parcel, flags);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user