Compare commits
15
Commits
039afc3213
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b2f7dfdb5 | ||
|
|
a34cccdf76 | ||
|
|
49789fc4a5 | ||
|
|
a6b23f0f3c | ||
|
|
47d735be2c | ||
|
|
74fb80f783 | ||
|
|
42148360e0 | ||
|
|
a32250fe34 | ||
|
|
88529c3634 | ||
|
|
8e5d88183c | ||
|
|
349eeb7025 | ||
|
|
6d131ed8e7 | ||
|
|
2f374d38c5 | ||
|
|
cfb5f5d367 | ||
|
|
117df5fedc |
+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
|
||||||
+5
-3
@@ -87,11 +87,13 @@ android_app {
|
|||||||
"1.0.001",
|
"1.0.001",
|
||||||
"--version-code",
|
"--version-code",
|
||||||
"10001040",
|
"10001040",
|
||||||
|
"--rename-manifest-package",
|
||||||
|
"com.bnkode.notie",
|
||||||
],
|
],
|
||||||
required: [
|
required: [
|
||||||
"libframesequence",
|
"libframesequence",
|
||||||
"libgiftranscode",
|
"libgiftranscode",
|
||||||
"com.android.messaging.allowlist",
|
"com.bnkode.notie.allowlist",
|
||||||
],
|
],
|
||||||
optimize: {
|
optimize: {
|
||||||
obfuscate: true,
|
obfuscate: true,
|
||||||
@@ -105,9 +107,9 @@ android_app {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prebuilt_etc {
|
prebuilt_etc {
|
||||||
name: "com.android.messaging.allowlist",
|
name: "com.bnkode.notie.allowlist",
|
||||||
product_specific: true,
|
product_specific: true,
|
||||||
sub_dir: "sysconfig",
|
sub_dir: "sysconfig",
|
||||||
src: "com.android.messaging.allowlist.xml",
|
src: "com.bnkode.notie.allowlist.xml",
|
||||||
filename_from_src: true,
|
filename_from_src: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
package="com.android.messaging"
|
||||||
android:installLocation="internalOnly">
|
android:installLocation="internalOnly">
|
||||||
|
|
||||||
<uses-sdk
|
|
||||||
android:minSdkVersion="33"
|
|
||||||
android:targetSdkVersion="34" />
|
|
||||||
|
|
||||||
<!-- Application holds CPU wakelock while working in background -->
|
<!-- Application holds CPU wakelock while working in background -->
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<!-- Application needs SMS/MMS permissions -->
|
<!-- Application needs SMS/MMS permissions -->
|
||||||
@@ -48,6 +44,12 @@
|
|||||||
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<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 -->
|
<!-- Optional features -->
|
||||||
<uses-feature
|
<uses-feature
|
||||||
@@ -65,9 +67,15 @@
|
|||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.screen.portrait"
|
android:name="android.hardware.screen.portrait"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.telephony"
|
android:name="android.hardware.telephony"
|
||||||
android:required="false" />
|
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>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
@@ -83,7 +91,9 @@
|
|||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:appCategory="social"
|
android:appCategory="social"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:localeConfig="@xml/locales_config"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/BugleTheme">
|
android:theme="@style/BugleTheme">
|
||||||
@@ -136,10 +146,18 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.conversationlist.ArchivedConversationListActivity"
|
android:name=".ui.conversationlist.ArchivedConversationListActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
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:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||||
android:screenOrientation="user"
|
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 -->
|
<!-- Displays the contents of a single conversation -->
|
||||||
<activity
|
<activity
|
||||||
@@ -159,6 +177,81 @@
|
|||||||
android:screenOrientation="user"
|
android:screenOrientation="user"
|
||||||
android:theme="@style/BugleTheme" />
|
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 -->
|
<!-- Full-screen photo viewer -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.photoviewer.BuglePhotoViewActivity"
|
android:name=".ui.photoviewer.BuglePhotoViewActivity"
|
||||||
@@ -175,7 +268,7 @@
|
|||||||
android:label="@string/settings_activity_title"
|
android:label="@string/settings_activity_title"
|
||||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||||
android:screenOrientation="user"
|
android:screenOrientation="user"
|
||||||
android:theme="@style/BugleTheme.SettingsActivity" />
|
android:theme="@style/Home.SubScreen" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.appsettings.PerSubscriptionSettingsActivity"
|
android:name=".ui.appsettings.PerSubscriptionSettingsActivity"
|
||||||
@@ -183,7 +276,7 @@
|
|||||||
android:label="@string/advanced_settings_activity_title"
|
android:label="@string/advanced_settings_activity_title"
|
||||||
android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity"
|
android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity"
|
||||||
android:screenOrientation="user"
|
android:screenOrientation="user"
|
||||||
android:theme="@style/BugleTheme.SettingsActivity" />
|
android:theme="@style/Home.SubScreen" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.appsettings.ApplicationSettingsActivity"
|
android:name=".ui.appsettings.ApplicationSettingsActivity"
|
||||||
@@ -192,7 +285,7 @@
|
|||||||
android:label="@string/general_settings_activity_title"
|
android:label="@string/general_settings_activity_title"
|
||||||
android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity"
|
android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity"
|
||||||
android:screenOrientation="user"
|
android:screenOrientation="user"
|
||||||
android:theme="@style/BugleTheme.SettingsActivity">
|
android:theme="@style/Home.SubScreen">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
@@ -227,22 +320,40 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- People & Options -->
|
<!-- Conversation profile -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.conversationsettings.PeopleAndOptionsActivity"
|
android:name=".ui.home.HomeProfileActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
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:parentActivityName="com.android.messaging.ui.conversation.ConversationActivity"
|
||||||
android:screenOrientation="user"
|
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 -->
|
<!-- License -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.LicenseActivity"
|
android:name=".ui.LicenseActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/menu_license"
|
android:label="@string/home_settings_licenses"
|
||||||
android:theme="@android:style/Theme.Holo.Light.Dialog">
|
android:theme="@style/Home.SubScreen" />
|
||||||
</activity>
|
|
||||||
|
|
||||||
<!-- Message Forwarding -->
|
<!-- Message Forwarding -->
|
||||||
<activity
|
<activity
|
||||||
@@ -263,7 +374,8 @@
|
|||||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:screenOrientation="user"
|
android:screenOrientation="user"
|
||||||
android:theme="@style/Invisible">
|
android:theme="@style/Home.TransparentSheetHost"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- VCard details -->
|
<!-- VCard details -->
|
||||||
@@ -286,20 +398,20 @@
|
|||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".datamodel.MessagingContentProvider"
|
android:name=".datamodel.MessagingContentProvider"
|
||||||
android:authorities="com.android.messaging.datamodel.MessagingContentProvider"
|
android:authorities="com.bnkode.notie.datamodel.MessagingContentProvider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".datamodel.MmsFileProvider"
|
android:name=".datamodel.MmsFileProvider"
|
||||||
android:authorities="com.android.messaging.datamodel.MmsFileProvider"
|
android:authorities="com.bnkode.notie.datamodel.MmsFileProvider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true" />
|
android:grantUriPermissions="true" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".datamodel.MediaScratchFileProvider"
|
android:name=".datamodel.MediaScratchFileProvider"
|
||||||
android:authorities="com.android.messaging.datamodel.MediaScratchFileProvider"
|
android:authorities="com.bnkode.notie.datamodel.MediaScratchFileProvider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true" />
|
android:grantUriPermissions="true" />
|
||||||
|
|
||||||
@@ -355,18 +467,18 @@
|
|||||||
android:name=".receiver.SendStatusReceiver"
|
android:name=".receiver.SendStatusReceiver"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<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" />
|
<data android:scheme="content" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<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>
|
||||||
<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" />
|
<data android:scheme="content" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<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" />
|
<data android:scheme="content" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
@@ -405,6 +517,14 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</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 -->
|
<!-- Broadcast receiver that will be notified to reset notifications -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receiver.NotificationReceiver"
|
android:name=".receiver.NotificationReceiver"
|
||||||
@@ -416,7 +536,7 @@
|
|||||||
android:name=".datamodel.action.ActionServiceImpl$PendingActionReceiver"
|
android:name=".datamodel.action.ActionServiceImpl$PendingActionReceiver"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.android.messaging.datamodel.PENDING_ACTION" />
|
<action android:name="com.bnkode.notie.datamodel.PENDING_ACTION" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</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>
|
||||||
+24
-1
@@ -36,6 +36,8 @@ import com.android.messaging.sms.BugleApnSettingsLoader;
|
|||||||
import com.android.messaging.sms.BugleUserAgentInfoLoader;
|
import com.android.messaging.sms.BugleUserAgentInfoLoader;
|
||||||
import com.android.messaging.sms.MmsConfig;
|
import com.android.messaging.sms.MmsConfig;
|
||||||
import com.android.messaging.ui.ConversationDrawables;
|
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.BuglePrefsKeys;
|
||||||
import com.android.messaging.util.LogUtil;
|
import com.android.messaging.util.LogUtil;
|
||||||
import com.android.messaging.util.PhoneUtils;
|
import com.android.messaging.util.PhoneUtils;
|
||||||
@@ -57,6 +59,8 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
|||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
FactoryImpl.register(getApplicationContext(), this);
|
FactoryImpl.register(getApplicationContext(), this);
|
||||||
|
HomeAppLock.install(this);
|
||||||
|
HomeToast.install(this);
|
||||||
|
|
||||||
sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
Thread.setDefaultUncaughtExceptionHandler(this);
|
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||||
@@ -124,6 +128,18 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
|||||||
Trace.endSection();
|
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
|
@Override
|
||||||
public void onLowMemory() {
|
public void onLowMemory() {
|
||||||
super.onLowMemory();
|
super.onLowMemory();
|
||||||
@@ -131,7 +147,14 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
|||||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||||
LogUtil.d(TAG, "BugleApplication.onLowMemory");
|
LogUtil.d(TAG, "BugleApplication.onLowMemory");
|
||||||
}
|
}
|
||||||
Factory.get().reclaimMemory();
|
reclaimCachedBitmaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void reclaimCachedBitmaps() {
|
||||||
|
final Factory factory = Factory.get();
|
||||||
|
if (factory != null) {
|
||||||
|
factory.reclaimMemory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
+25
-1
@@ -533,6 +533,7 @@ public class BugleDatabaseOperations {
|
|||||||
// the sender is blocked, or we have been told to keep it archived.
|
// the sender is blocked, or we have been told to keep it archived.
|
||||||
if (!keepArchived) {
|
if (!keepArchived) {
|
||||||
values.put(ConversationColumns.ARCHIVE_STATUS, 0);
|
values.put(ConversationColumns.ARCHIVE_STATUS, 0);
|
||||||
|
values.put(ConversationColumns.TRASH_TIMESTAMP, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final MessageData message = readMessage(dbWrapper, messageId);
|
final MessageData message = readMessage(dbWrapper, messageId);
|
||||||
@@ -575,6 +576,20 @@ public class BugleDatabaseOperations {
|
|||||||
updateConversationRowIfExists(dbWrapper, conversationId, values);
|
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,
|
static void addSnippetTextAndPreviewToContentValues(final MessageData message,
|
||||||
final boolean showDraft, final ContentValues values) {
|
final boolean showDraft, final ContentValues values) {
|
||||||
values.put(ConversationColumns.SHOW_DRAFT, showDraft ? 1 : 0);
|
values.put(ConversationColumns.SHOW_DRAFT, showDraft ? 1 : 0);
|
||||||
@@ -685,7 +700,16 @@ public class BugleDatabaseOperations {
|
|||||||
sortTimestamp = cursor.getLong(1);
|
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();
|
final ContentValues values = new ContentValues();
|
||||||
if (draftMessage == null || !draftMessage.hasContent()) {
|
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.MmsSmsUtils;
|
||||||
import com.android.messaging.sms.MmsUtils;
|
import com.android.messaging.sms.MmsUtils;
|
||||||
import com.android.messaging.ui.UIIntents;
|
import com.android.messaging.ui.UIIntents;
|
||||||
|
import com.android.messaging.ui.home.MutedConversationStore;
|
||||||
import com.android.messaging.util.Assert;
|
import com.android.messaging.util.Assert;
|
||||||
import com.android.messaging.util.AvatarUriUtil;
|
import com.android.messaging.util.AvatarUriUtil;
|
||||||
import com.android.messaging.util.BugleGservicesKeys;
|
import com.android.messaging.util.BugleGservicesKeys;
|
||||||
@@ -354,6 +355,9 @@ public class BugleNotifications {
|
|||||||
final Context context = Factory.get().getApplicationContext();
|
final Context context = Factory.get().getApplicationContext();
|
||||||
// TODO: Need to fix this for multi conversation notifications to rate limit dings.
|
// TODO: Need to fix this for multi conversation notifications to rate limit dings.
|
||||||
final String conversationId = state.mConversationIds.first();
|
final String conversationId = state.mConversationIds.first();
|
||||||
|
if (MutedConversationStore.isMuted(context, conversationId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
String id = NotificationsUtil.DEFAULT_CHANNEL_ID;
|
String id = NotificationsUtil.DEFAULT_CHANNEL_ID;
|
||||||
if (NotificationsUtil.getNotificationChannel(context, conversationId) != null) {
|
if (NotificationsUtil.getNotificationChannel(context, conversationId) != null) {
|
||||||
id = conversationId;
|
id = conversationId;
|
||||||
@@ -535,7 +539,10 @@ public class BugleNotifications {
|
|||||||
private static void createMessageNotification(final boolean silent,
|
private static void createMessageNotification(final boolean silent,
|
||||||
final String conversationId) {
|
final String conversationId) {
|
||||||
final NotificationState state = MessageNotificationState.getNotificationState();
|
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) {
|
if (state == null) {
|
||||||
cancel(PendingIntentConstants.SMS_NOTIFICATION_ID);
|
cancel(PendingIntentConstants.SMS_NOTIFICATION_ID);
|
||||||
if (softSound && !TextUtils.isEmpty(conversationId)) {
|
if (softSound && !TextUtils.isEmpty(conversationId)) {
|
||||||
@@ -718,7 +725,9 @@ public class BugleNotifications {
|
|||||||
setWearableGroupOptions(notifBuilder, notificationState);
|
setWearableGroupOptions(notifBuilder, notificationState);
|
||||||
|
|
||||||
if (notificationState instanceof MultiMessageNotificationState) {
|
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
|
// 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
|
// 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
|
// 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 int requestCode = multiMessageNotificationState.getReplyIntentRequestCode();
|
||||||
final PendingIntent replyPendingIntent = UIIntents.get()
|
final PendingIntent replyPendingIntent = UIIntents.get()
|
||||||
.getPendingIntentForSendingMessageToConversation(context,
|
.getPendingIntentForSendingMessageToConversation(context,
|
||||||
conversationId, selfId, requiresMms, requestCode);
|
conversationId, selfId, requiresMms,
|
||||||
|
multiMessageNotificationState.mTitle, requestCode);
|
||||||
|
|
||||||
final int replyLabelRes = requiresMms ? R.string.notification_reply_via_mms :
|
// Phone: open the Home bottom-sheet composer. Wear keeps RemoteInput.
|
||||||
R.string.notification_reply_via_sms;
|
final NotificationCompat.Action replyAction =
|
||||||
|
|
||||||
final NotificationCompat.Action.Builder actionBuilder =
|
|
||||||
new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
||||||
context.getString(replyLabelRes), replyPendingIntent);
|
context.getString(R.string.home_notification_reply), replyPendingIntent)
|
||||||
final RemoteInput.Builder remoteInputBuilder = new RemoteInput.Builder(Intent.EXTRA_TEXT);
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||||
remoteInputBuilder.setLabel(context.getString(R.string.notification_reply_prompt));
|
.setShowsUserInterface(true)
|
||||||
actionBuilder.addRemoteInput(remoteInputBuilder.build());
|
.build();
|
||||||
notifBuilder.addAction(actionBuilder.build());
|
notifBuilder.addAction(replyAction);
|
||||||
|
|
||||||
// Support the action on a wearable device
|
// 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(
|
final String[] choices = context.getResources().getStringArray(
|
||||||
R.array.notification_reply_choices);
|
R.array.notification_reply_choices);
|
||||||
remoteInputBuilder.setChoices(choices);
|
final RemoteInput wearRemoteInput = new RemoteInput.Builder(Intent.EXTRA_TEXT)
|
||||||
wearActionBuilder.addRemoteInput(remoteInputBuilder.build());
|
.setLabel(context.getString(R.string.home_notification_reply_hint))
|
||||||
wearableExtender.addAction(wearActionBuilder.build());
|
.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,
|
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.action.BackgroundWorker;
|
||||||
import com.android.messaging.datamodel.data.BlockedParticipantsData;
|
import com.android.messaging.datamodel.data.BlockedParticipantsData;
|
||||||
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
|
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.ContactListItemData;
|
||||||
import com.android.messaging.datamodel.data.ContactPickerData;
|
import com.android.messaging.datamodel.data.ContactPickerData;
|
||||||
import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
|
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.MessagePartData;
|
||||||
import com.android.messaging.datamodel.data.ParticipantData;
|
import com.android.messaging.datamodel.data.ParticipantData;
|
||||||
import com.android.messaging.datamodel.data.ParticipantListItemData;
|
import com.android.messaging.datamodel.data.ParticipantListItemData;
|
||||||
import com.android.messaging.datamodel.data.PeopleAndOptionsData;
|
import com.android.messaging.datamodel.data.ConversationProfileData;
|
||||||
import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
|
import com.android.messaging.datamodel.data.ConversationProfileData.ConversationProfileDataListener;
|
||||||
import com.android.messaging.datamodel.data.PeopleOptionsItemData;
|
|
||||||
import com.android.messaging.datamodel.data.SettingsData;
|
import com.android.messaging.datamodel.data.SettingsData;
|
||||||
import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
|
import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
|
||||||
import com.android.messaging.datamodel.data.SubscriptionListData;
|
import com.android.messaging.datamodel.data.SubscriptionListData;
|
||||||
@@ -67,8 +68,14 @@ public abstract class DataModel {
|
|||||||
get().getActionService().scheduleAction(action, code, delayMs);
|
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,
|
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,
|
public abstract ConversationData createConversationData(final Context context,
|
||||||
final ConversationDataListener listener, final String conversationId);
|
final ConversationDataListener listener, final String conversationId);
|
||||||
@@ -83,10 +90,9 @@ public abstract class DataModel {
|
|||||||
public abstract LaunchConversationData createLaunchConversationData(
|
public abstract LaunchConversationData createLaunchConversationData(
|
||||||
LaunchConversationDataListener listener);
|
LaunchConversationDataListener listener);
|
||||||
|
|
||||||
public abstract PeopleOptionsItemData createPeopleOptionsItemData(final Context context);
|
public abstract ConversationProfileData createConversationProfileData(
|
||||||
|
final String conversationId, final Context context,
|
||||||
public abstract PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
|
final ConversationProfileDataListener listener);
|
||||||
final Context context, final PeopleAndOptionsDataListener listener);
|
|
||||||
|
|
||||||
public abstract VCardContactItemData createVCardContactItemData(final Context context,
|
public abstract VCardContactItemData createVCardContactItemData(final Context context,
|
||||||
final MessagePartData data);
|
final MessagePartData data);
|
||||||
@@ -100,6 +106,9 @@ public abstract class DataModel {
|
|||||||
public abstract BlockedParticipantsData createBlockedParticipantsData(Context context,
|
public abstract BlockedParticipantsData createBlockedParticipantsData(Context context,
|
||||||
BlockedParticipantsDataListener listener);
|
BlockedParticipantsDataListener listener);
|
||||||
|
|
||||||
|
public abstract ConversationSearchData createConversationSearchData(Context context,
|
||||||
|
String conversationId, ConversationSearchDataListener listener);
|
||||||
|
|
||||||
public abstract SubscriptionListData createSubscriptonListData(Context context);
|
public abstract SubscriptionListData createSubscriptonListData(Context context);
|
||||||
|
|
||||||
public abstract SettingsData createSettingsData(Context context, SettingsDataListener listener);
|
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.BackgroundWorker;
|
||||||
import com.android.messaging.datamodel.action.FixupMessageStatusOnStartupAction;
|
import com.android.messaging.datamodel.action.FixupMessageStatusOnStartupAction;
|
||||||
import com.android.messaging.datamodel.action.ProcessPendingMessagesAction;
|
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;
|
||||||
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
|
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.ContactListItemData;
|
||||||
import com.android.messaging.datamodel.data.ContactPickerData;
|
import com.android.messaging.datamodel.data.ContactPickerData;
|
||||||
import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
|
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.MessagePartData;
|
||||||
import com.android.messaging.datamodel.data.ParticipantData;
|
import com.android.messaging.datamodel.data.ParticipantData;
|
||||||
import com.android.messaging.datamodel.data.ParticipantListItemData;
|
import com.android.messaging.datamodel.data.ParticipantListItemData;
|
||||||
import com.android.messaging.datamodel.data.PeopleAndOptionsData;
|
import com.android.messaging.datamodel.data.ConversationProfileData;
|
||||||
import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
|
import com.android.messaging.datamodel.data.ConversationProfileData.ConversationProfileDataListener;
|
||||||
import com.android.messaging.datamodel.data.PeopleOptionsItemData;
|
|
||||||
import com.android.messaging.datamodel.data.SettingsData;
|
import com.android.messaging.datamodel.data.SettingsData;
|
||||||
import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
|
import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
|
||||||
import com.android.messaging.datamodel.data.SubscriptionListData;
|
import com.android.messaging.datamodel.data.SubscriptionListData;
|
||||||
@@ -80,8 +82,9 @@ public class DataModelImpl extends DataModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConversationListData createConversationListData(final Context context,
|
public ConversationListData createConversationListData(final Context context,
|
||||||
final ConversationListDataListener listener, final boolean archivedMode) {
|
final ConversationListDataListener listener, final boolean archivedMode,
|
||||||
return new ConversationListData(context, listener, archivedMode);
|
final boolean trashMode) {
|
||||||
|
return new ConversationListData(context, listener, archivedMode, trashMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -107,6 +110,12 @@ public class DataModelImpl extends DataModel {
|
|||||||
return new BlockedParticipantsData(context, listener);
|
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
|
@Override
|
||||||
public MediaPickerData createMediaPickerData(final Context context) {
|
public MediaPickerData createMediaPickerData(final Context context) {
|
||||||
return new MediaPickerData(context);
|
return new MediaPickerData(context);
|
||||||
@@ -119,14 +128,9 @@ public class DataModelImpl extends DataModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) {
|
public ConversationProfileData createConversationProfileData(final String conversationId,
|
||||||
return new PeopleOptionsItemData(context);
|
final Context context, final ConversationProfileDataListener listener) {
|
||||||
}
|
return new ConversationProfileData(conversationId, context, listener);
|
||||||
|
|
||||||
@Override
|
|
||||||
public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
|
|
||||||
final Context context, final PeopleAndOptionsDataListener listener) {
|
|
||||||
return new PeopleAndOptionsData(conversationId, context, listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -209,6 +213,7 @@ public class DataModelImpl extends DataModel {
|
|||||||
createConnectivityUtilForEachActiveSubscription();
|
createConnectivityUtilForEachActiveSubscription();
|
||||||
|
|
||||||
FixupMessageStatusOnStartupAction.fixupMessageStatus();
|
FixupMessageStatusOnStartupAction.fixupMessageStatus();
|
||||||
|
PurgeExpiredTrashAction.purgeExpired();
|
||||||
ProcessPendingMessagesAction.processFirstPendingMessage();
|
ProcessPendingMessagesAction.processFirstPendingMessage();
|
||||||
SyncManager.immediateSync();
|
SyncManager.immediateSync();
|
||||||
|
|
||||||
+10
@@ -101,6 +101,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
|||||||
/* If this conversation is archived */
|
/* If this conversation is archived */
|
||||||
public static final String ARCHIVE_STATUS = "archive_status";
|
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 */
|
/* Timestamp for sorting purposes */
|
||||||
public static final String SORT_TIMESTAMP = "sort_timestamp";
|
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_URI + " TEXT, "
|
||||||
+ ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE + " TEXT, "
|
+ ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE + " TEXT, "
|
||||||
+ ConversationColumns.ARCHIVE_STATUS + " INT DEFAULT(0), "
|
+ ConversationColumns.ARCHIVE_STATUS + " INT DEFAULT(0), "
|
||||||
|
+ ConversationColumns.TRASH_TIMESTAMP + " INT DEFAULT(0), "
|
||||||
+ ConversationColumns.SORT_TIMESTAMP + " INT DEFAULT(0), "
|
+ ConversationColumns.SORT_TIMESTAMP + " INT DEFAULT(0), "
|
||||||
+ ConversationColumns.LAST_READ_TIMESTAMP + " INT DEFAULT(0), "
|
+ ConversationColumns.LAST_READ_TIMESTAMP + " INT DEFAULT(0), "
|
||||||
+ ConversationColumns.ICON + " TEXT, "
|
+ ConversationColumns.ICON + " TEXT, "
|
||||||
@@ -183,6 +187,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
|||||||
+ " ON " + CONVERSATIONS_TABLE
|
+ " ON " + CONVERSATIONS_TABLE
|
||||||
+ "(" + ConversationColumns.ARCHIVE_STATUS + ")";
|
+ "(" + 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 =
|
private static final String CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL =
|
||||||
"CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SORT_TIMESTAMP
|
"CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SORT_TIMESTAMP
|
||||||
+ " ON " + CONVERSATIONS_TABLE
|
+ " ON " + CONVERSATIONS_TABLE
|
||||||
@@ -525,6 +534,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
|||||||
private static final String[] CREATE_INDEX_SQLS = new String[] {
|
private static final String[] CREATE_INDEX_SQLS = new String[] {
|
||||||
CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL,
|
CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL,
|
||||||
CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL,
|
CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL,
|
||||||
|
CONVERSATIONS_TABLE_TRASH_TIMESTAMP_INDEX_SQL,
|
||||||
CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL,
|
CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL,
|
||||||
MESSAGES_TABLE_SORT_INDEX_SQL,
|
MESSAGES_TABLE_SORT_INDEX_SQL,
|
||||||
MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL,
|
MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL,
|
||||||
+14
@@ -48,6 +48,9 @@ public class DatabaseUpgradeHelper {
|
|||||||
if (currentVersion < 2) {
|
if (currentVersion < 2) {
|
||||||
currentVersion = upgradeToVersion2(db);
|
currentVersion = upgradeToVersion2(db);
|
||||||
}
|
}
|
||||||
|
if (currentVersion < 3) {
|
||||||
|
currentVersion = upgradeToVersion3(db);
|
||||||
|
}
|
||||||
// Rebuild all the views
|
// Rebuild all the views
|
||||||
final Context context = Factory.get().getApplicationContext();
|
final Context context = Factory.get().getApplicationContext();
|
||||||
DatabaseHelper.dropAllViews(db);
|
DatabaseHelper.dropAllViews(db);
|
||||||
@@ -63,6 +66,17 @@ public class DatabaseUpgradeHelper {
|
|||||||
return 2;
|
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
|
* 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
|
* 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();
|
final String fileId = uri.getPath();
|
||||||
if (isValidFileId(fileId)) {
|
if (isValidFileId(fileId)) {
|
||||||
final File file = getFile(fileId, getExtensionFromUri(uri));
|
final File file = getFile(fileId, getExtensionFromUri(uri));
|
||||||
return file.delete() ? 1 : 0;
|
return file != null && file.delete() ? 1 : 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -114,6 +114,9 @@ public abstract class FileProvider extends ContentProvider {
|
|||||||
final String fileId = uri.getPath();
|
final String fileId = uri.getPath();
|
||||||
if (isValidFileId(fileId)) {
|
if (isValidFileId(fileId)) {
|
||||||
final File file = getFile(fileId, getExtensionFromUri(uri));
|
final File file = getFile(fileId, getExtensionFromUri(uri));
|
||||||
|
if (file == null) {
|
||||||
|
throw new FileNotFoundException(String.valueOf(uri));
|
||||||
|
}
|
||||||
final int mode =
|
final int mode =
|
||||||
(TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
|
(TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
|
||||||
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
|
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
|
||||||
+2
@@ -120,6 +120,8 @@ public class FrequentContactsCursorBuilder {
|
|||||||
mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
|
mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
|
||||||
row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] =
|
row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] =
|
||||||
mAllContactsCursor.getString(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;
|
boolean numberAlreadyAdded = false;
|
||||||
for (Object[] oldRow : rows) {
|
for (Object[] oldRow : rows) {
|
||||||
+8
-4
@@ -46,7 +46,7 @@ public class MediaScratchFileProvider extends FileProvider {
|
|||||||
private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap = new SimpleArrayMap<>();
|
private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap = new SimpleArrayMap<>();
|
||||||
|
|
||||||
public static final String AUTHORITY =
|
public static final String AUTHORITY =
|
||||||
"com.android.messaging.datamodel.MediaScratchFileProvider";
|
"com.bnkode.notie.datamodel.MediaScratchFileProvider";
|
||||||
private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace";
|
private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace";
|
||||||
|
|
||||||
public static boolean isMediaScratchSpaceUri(final Uri uri) {
|
public static boolean isMediaScratchSpaceUri(final Uri uri) {
|
||||||
@@ -68,8 +68,8 @@ public class MediaScratchFileProvider extends FileProvider {
|
|||||||
public static Uri buildMediaScratchSpaceUri(final String extension) {
|
public static Uri buildMediaScratchSpaceUri(final String extension) {
|
||||||
final Uri uri = FileProvider.buildFileUri(AUTHORITY, extension);
|
final Uri uri = FileProvider.buildFileUri(AUTHORITY, extension);
|
||||||
final File file = getFileWithExtension(uri.getPath(), extension);
|
final File file = getFileWithExtension(uri.getPath(), extension);
|
||||||
if (!ensureFileExists(file)) {
|
if (file == null || !ensureFileExists(file)) {
|
||||||
LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath());
|
LogUtil.e(TAG, "Failed to create temp file for " + uri);
|
||||||
}
|
}
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
@@ -90,8 +90,12 @@ public class MediaScratchFileProvider extends FileProvider {
|
|||||||
|
|
||||||
private static File getFileWithExtension(final String path, final String extension) {
|
private static File getFileWithExtension(final String path, final String extension) {
|
||||||
final Context context = Factory.get().getApplicationContext();
|
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),
|
final File filePath = new File(getDirectory(context),
|
||||||
TextUtils.isEmpty(extension) ? path : path + "." + extension);
|
TextUtils.isEmpty(extension) ? name : name + "." + extension);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!filePath.getCanonicalPath()
|
if (!filePath.getCanonicalPath()
|
||||||
+58
-73
@@ -39,6 +39,7 @@ import androidx.core.app.NotificationCompat;
|
|||||||
import androidx.core.app.NotificationCompat.Builder;
|
import androidx.core.app.NotificationCompat.Builder;
|
||||||
import androidx.core.app.NotificationCompat.WearableExtender;
|
import androidx.core.app.NotificationCompat.WearableExtender;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.app.Person;
|
||||||
|
|
||||||
import com.android.messaging.Factory;
|
import com.android.messaging.Factory;
|
||||||
import com.android.messaging.R;
|
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.datamodel.data.ParticipantData;
|
||||||
import com.android.messaging.sms.MmsUtils;
|
import com.android.messaging.sms.MmsUtils;
|
||||||
import com.android.messaging.ui.UIIntents;
|
import com.android.messaging.ui.UIIntents;
|
||||||
|
import com.android.messaging.ui.home.MutedConversationStore;
|
||||||
import com.android.messaging.util.Assert;
|
import com.android.messaging.util.Assert;
|
||||||
import com.android.messaging.util.AvatarUriUtil;
|
import com.android.messaging.util.AvatarUriUtil;
|
||||||
import com.android.messaging.util.BugleGservicesKeys;
|
import com.android.messaging.util.BugleGservicesKeys;
|
||||||
@@ -494,85 +496,62 @@ public abstract class MessageNotificationState extends NotificationState {
|
|||||||
@Override
|
@Override
|
||||||
protected NotificationCompat.Style build(final Builder builder) {
|
protected NotificationCompat.Style build(final Builder builder) {
|
||||||
builder.setContentTitle(mTitle)
|
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 ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
|
||||||
final List<NotificationLineInfo> lineInfos = convInfo.mLineInfos;
|
final List<NotificationLineInfo> lineInfos = convInfo.mLineInfos;
|
||||||
final int messageCount = lineInfos.size();
|
final Person user = new Person.Builder()
|
||||||
// At this point, all the messages come from the same conversation. We need to load
|
.setName(context.getString(R.string.home_notification_you))
|
||||||
// the sender's avatar and then finish building the notification on a callback.
|
.build();
|
||||||
|
final NotificationCompat.MessagingStyle style =
|
||||||
builder.setContentText(mContent); // for collapsed state
|
new NotificationCompat.MessagingStyle(user);
|
||||||
|
if (convInfo.mIsGroup) {
|
||||||
if (messageCount == 1) {
|
style.setGroupConversation(true);
|
||||||
final boolean shouldShowImage = ContentType.isImageType(mAttachmentType)
|
if (!TextUtils.isEmpty(convInfo.mGroupConversationName)) {
|
||||||
|| (ContentType.isVideoType(mAttachmentType));
|
style.setConversationTitle(convInfo.mGroupConversationName);
|
||||||
if (mAttachmentUri != null && shouldShowImage) {
|
|
||||||
// Show "Picture" as the content
|
|
||||||
final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfos.get(0);
|
|
||||||
String authorFirstName = messageLineInfo.mAuthorFirstName;
|
|
||||||
|
|
||||||
// For the collapsed state, just show "picture" unless this is a
|
|
||||||
// group conversation. If it's a group, show the sender name and
|
|
||||||
// "picture".
|
|
||||||
final CharSequence tickerTag =
|
|
||||||
BugleNotifications.formatAttachmentTag(authorFirstName,
|
|
||||||
mAttachmentType);
|
|
||||||
// For 1:1 notifications don't show first name in the notification, but
|
|
||||||
// do show it in the ticker text
|
|
||||||
CharSequence pictureTag = tickerTag;
|
|
||||||
if (!convInfo.mIsGroup) {
|
|
||||||
authorFirstName = null;
|
|
||||||
pictureTag = BugleNotifications.formatAttachmentTag(authorFirstName,
|
|
||||||
mAttachmentType);
|
|
||||||
}
|
|
||||||
builder.setContentText(pictureTag);
|
|
||||||
builder.setTicker(tickerTag);
|
|
||||||
|
|
||||||
notifStyle = new NotificationCompat.BigPictureStyle(builder)
|
|
||||||
.setSummaryText(BugleNotifications.formatInboxMessage(
|
|
||||||
authorFirstName,
|
|
||||||
null, null,
|
|
||||||
null)); // expanded state, just show sender
|
|
||||||
} else {
|
|
||||||
notifStyle = new NotificationCompat.BigTextStyle(builder)
|
|
||||||
.bigText(mContent);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// We've got multiple messages for the same sender.
|
|
||||||
// Starting with the oldest new message, display the full text of each message.
|
|
||||||
// Begin a line for each subsequent message.
|
|
||||||
final SpannableStringBuilder buf = new SpannableStringBuilder();
|
|
||||||
|
|
||||||
for (int i = lineInfos.size() - 1; i >= 0; --i) {
|
|
||||||
final NotificationLineInfo info = lineInfos.get(i);
|
|
||||||
final MessageLineInfo messageLineInfo = (MessageLineInfo) info;
|
|
||||||
mAttachmentUri = messageLineInfo.mAttachmentUri;
|
|
||||||
mAttachmentType = messageLineInfo.mAttachmentType;
|
|
||||||
CharSequence text = messageLineInfo.mText;
|
|
||||||
if (!TextUtils.isEmpty(text) || mAttachmentUri != null) {
|
|
||||||
if (convInfo.mIsGroup) {
|
|
||||||
// append the bold name to the front of the message
|
|
||||||
text = BugleNotifications.buildSpaceSeparatedMessage(
|
|
||||||
messageLineInfo.mAuthorFullName, text, mAttachmentUri,
|
|
||||||
mAttachmentType);
|
|
||||||
} else {
|
|
||||||
text = BugleNotifications.buildSpaceSeparatedMessage(
|
|
||||||
null, text, mAttachmentUri, mAttachmentType);
|
|
||||||
}
|
|
||||||
buf.append(text);
|
|
||||||
if (i > 0) {
|
|
||||||
buf.append('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a single notification -- big style with the text of all the messages
|
|
||||||
notifStyle = new NotificationCompat.BigTextStyle(builder).bigText(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
CharSequence text = messageLineInfo.mText;
|
||||||
|
if (TextUtils.isEmpty(text) && messageLineInfo.mAttachmentUri != null) {
|
||||||
|
text = BugleNotifications.formatAttachmentTag(
|
||||||
|
convInfo.mIsGroup ? messageLineInfo.mAuthorFirstName : null,
|
||||||
|
messageLineInfo.mAttachmentType);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
builder.setWhen(convInfo.mReceivedTimestamp);
|
builder.setWhen(convInfo.mReceivedTimestamp);
|
||||||
return notifStyle;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -824,6 +803,9 @@ public abstract class MessageNotificationState extends NotificationState {
|
|||||||
final String messageText = convMessageData.getText();
|
final String messageText = convMessageData.getText();
|
||||||
|
|
||||||
final String convId = convMessageData.getConversationId();
|
final String convId = convMessageData.getConversationId();
|
||||||
|
if (MutedConversationStore.isMuted(context, convId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
final String messageId = convMessageData.getMessageId();
|
final String messageId = convMessageData.getMessageId();
|
||||||
|
|
||||||
CharSequence text = messageText;
|
CharSequence text = messageText;
|
||||||
@@ -840,6 +822,9 @@ public abstract class MessageNotificationState extends NotificationState {
|
|||||||
if (currConvInfo == null) {
|
if (currConvInfo == null) {
|
||||||
final ConversationListItemData convData =
|
final ConversationListItemData convData =
|
||||||
ConversationListItemData.getExistingConversation(db, convId);
|
ConversationListItemData.getExistingConversation(db, convId);
|
||||||
|
if (convData == null || convData.getIsTrashed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
final int subId = BugleDatabaseOperations.getSelfSubscriptionId(db,
|
final int subId = BugleDatabaseOperations.getSelfSubscriptionId(db,
|
||||||
convData.getSelfId());
|
convData.getSelfId());
|
||||||
groupConversationName = convData.getName();
|
groupConversationName = convData.getName();
|
||||||
+51
-1
@@ -53,7 +53,7 @@ public class MessagingContentProvider extends ContentProvider {
|
|||||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||||
|
|
||||||
public static final String AUTHORITY =
|
public static final String AUTHORITY =
|
||||||
"com.android.messaging.datamodel.MessagingContentProvider";
|
"com.bnkode.notie.datamodel.MessagingContentProvider";
|
||||||
private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/';
|
private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/';
|
||||||
|
|
||||||
// Conversations query
|
// Conversations query
|
||||||
@@ -70,6 +70,11 @@ public class MessagingContentProvider extends ContentProvider {
|
|||||||
public static final Uri CONVERSATION_MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY +
|
public static final Uri CONVERSATION_MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY +
|
||||||
MESSAGES_QUERY + "/conversation");
|
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
|
// Conversation participants query
|
||||||
private static final String PARTICIPANTS_QUERY = "participants";
|
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_QUERY_CODE = 20;
|
||||||
private static final int CONVERSATION_MESSAGES_QUERY_CODE = 30;
|
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_PARTICIPANTS_QUERY_CODE = 40;
|
||||||
private static final int CONVERSATION_IMAGES_QUERY_CODE = 50;
|
private static final int CONVERSATION_IMAGES_QUERY_CODE = 50;
|
||||||
private static final int DRAFT_IMAGES_QUERY_CODE = 60;
|
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, CONVERSATIONS_QUERY + "/*", CONVERSATION_QUERY_CODE);
|
||||||
sURIMatcher.addURI(AUTHORITY, MESSAGES_QUERY + "/conversation/*",
|
sURIMatcher.addURI(AUTHORITY, MESSAGES_QUERY + "/conversation/*",
|
||||||
CONVERSATION_MESSAGES_QUERY_CODE);
|
CONVERSATION_MESSAGES_QUERY_CODE);
|
||||||
|
sURIMatcher.addURI(AUTHORITY, MESSAGES_QUERY + "/search/*",
|
||||||
|
CONVERSATION_MESSAGES_SEARCH_QUERY_CODE);
|
||||||
sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY + "/conversation/*",
|
sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY + "/conversation/*",
|
||||||
CONVERSATION_PARTICIPANTS_QUERY_CODE);
|
CONVERSATION_PARTICIPANTS_QUERY_CODE);
|
||||||
sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY, PARTICIPANTS_QUERY_CODE);
|
sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY, PARTICIPANTS_QUERY_CODE);
|
||||||
@@ -176,6 +184,17 @@ public class MessagingContentProvider extends ContentProvider {
|
|||||||
return builder.build();
|
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) {
|
public static void notifyMessagesChanged(final String conversationId) {
|
||||||
final Uri uri = buildConversationMessagesUri(conversationId);
|
final Uri uri = buildConversationMessagesUri(conversationId);
|
||||||
final Context context = Factory.get().getApplicationContext();
|
final Context context = Factory.get().getApplicationContext();
|
||||||
@@ -319,6 +338,19 @@ public class MessagingContentProvider extends ContentProvider {
|
|||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Malformed URI " + uri);
|
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:
|
case CONVERSATION_IMAGES_QUERY_CODE:
|
||||||
queryBuilder.setTables(ConversationImagePartsView.getViewName());
|
queryBuilder.setTables(ConversationImagePartsView.getViewName());
|
||||||
if (uri.getPathSegments().size() == 2) {
|
if (uri.getPathSegments().size() == 2) {
|
||||||
@@ -366,6 +398,24 @@ public class MessagingContentProvider extends ContentProvider {
|
|||||||
return cursor;
|
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
|
@Override
|
||||||
public String getType(@NonNull final Uri uri) {
|
public String getType(@NonNull final Uri uri) {
|
||||||
final StringBuilder sb = new
|
final StringBuilder sb = new
|
||||||
+1
-1
@@ -32,7 +32,7 @@ import java.io.IOException;
|
|||||||
public class MmsFileProvider extends FileProvider {
|
public class MmsFileProvider extends FileProvider {
|
||||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
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";
|
private static final String RAW_MMS_DIR = "rawmms";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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
-1
@@ -137,7 +137,7 @@ public class ActionServiceImpl extends JobIntentService {
|
|||||||
* Broadcast receiver for alarms scheduled through ActionService.
|
* Broadcast receiver for alarms scheduled through ActionService.
|
||||||
*/
|
*/
|
||||||
public static class PendingActionReceiver extends BroadcastReceiver {
|
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.
|
* Allocate an intent with a specific opcode and alarm action.
|
||||||
+6
-6
@@ -22,6 +22,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.android.messaging.Factory;
|
import com.android.messaging.Factory;
|
||||||
import com.android.messaging.R;
|
import com.android.messaging.R;
|
||||||
|
import com.android.messaging.ui.home.HomeToast;
|
||||||
import com.android.messaging.datamodel.DataModel;
|
import com.android.messaging.datamodel.DataModel;
|
||||||
import com.android.messaging.datamodel.data.MessageData;
|
import com.android.messaging.datamodel.data.MessageData;
|
||||||
import com.android.messaging.datamodel.data.ParticipantData;
|
import com.android.messaging.datamodel.data.ParticipantData;
|
||||||
@@ -115,13 +116,13 @@ public class BugleActionToasts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void showToast(final int messageResId) {
|
private static void showToast(final int messageResId) {
|
||||||
ThreadUtil.getMainThreadHandler().post(() -> Toast.makeText(getApplicationContext(),
|
ThreadUtil.getMainThreadHandler().post(() -> HomeToast.show(getApplicationContext(),
|
||||||
getApplicationContext().getString(messageResId), Toast.LENGTH_LONG).show());
|
messageResId, Toast.LENGTH_LONG));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showToast(final String message) {
|
private static void showToast(final String message) {
|
||||||
ThreadUtil.getMainThreadHandler().post(() ->
|
ThreadUtil.getMainThreadHandler().post(() ->
|
||||||
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show());
|
HomeToast.show(getApplicationContext(), message, Toast.LENGTH_LONG));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Context getApplicationContext() {
|
private static Context getApplicationContext() {
|
||||||
@@ -143,12 +144,11 @@ public class BugleActionToasts {
|
|||||||
final boolean block,
|
final boolean block,
|
||||||
final String destination) {
|
final String destination) {
|
||||||
if (success) {
|
if (success) {
|
||||||
Toast.makeText(mContext,
|
HomeToast.show(mContext,
|
||||||
block
|
block
|
||||||
? R.string.update_destination_blocked
|
? R.string.update_destination_blocked
|
||||||
: R.string.update_destination_unblocked,
|
: R.string.update_destination_unblocked,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG);
|
||||||
).show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+8
@@ -132,6 +132,13 @@ public class InsertNewMessageAction extends Action implements Parcelable {
|
|||||||
final DatabaseWrapper db = DataModel.get().getDatabase();
|
final DatabaseWrapper db = DataModel.get().getDatabase();
|
||||||
final String conversationId = message.getConversationId();
|
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);
|
final ParticipantData self = getSelf(db, conversationId, message);
|
||||||
if (self == null) {
|
if (self == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -190,6 +197,7 @@ public class InsertNewMessageAction extends Action implements Parcelable {
|
|||||||
messageToSend, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT);
|
messageToSend, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT);
|
||||||
}
|
}
|
||||||
MessagingContentProvider.notifyConversationListChanged();
|
MessagingContentProvider.notifyConversationListChanged();
|
||||||
|
MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
|
||||||
ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
|
ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
|
||||||
|
|
||||||
return message;
|
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