diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 8c01bfb4fa..bd9e8d6aaa 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -18,6 +18,8 @@ jobs:
steps:
- uses: actions/checkout@v3
+ with:
+ submodules: true
- name: set up JDK 17
uses: actions/setup-java@v3
diff --git a/.github/workflows/diffuse.yml b/.github/workflows/diffuse.yml
index c1f99dcef1..b55a9bd08c 100644
--- a/.github/workflows/diffuse.yml
+++ b/.github/workflows/diffuse.yml
@@ -15,6 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
+ submodules: true
ref: ${{ github.event.pull_request.base.sha }}
- name: set up JDK 17
@@ -45,6 +46,7 @@ jobs:
- uses: actions/checkout@v3
with:
+ submodules: true
clean: 'false'
- name: Build with Gradle
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000..926b4d7770
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "libwebp"]
+ path = libwebp
+ url = git@github.com:webmproject/libwebp.git
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index adffc8f63a..10c8caf63c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,6 +15,12 @@ Truths which we believe to be self-evident:
1. **There is no such thing as time.** Protocol ideas that require synchronized clocks are doomed to failure.
+## Building
+
+1. You'll need to get the `libwebp` submodule after checking out the repository with `git submodule init && git submodule update`
+1. Most things are pretty straightforward, and opening the project in Android Studio should get you most of the way there.
+1. Depending on your configuration, you'll also likely need to install additional SDK Tool components, namely the versions of NDK and CMake we are currently using in our [Docker](https://github.com/signalapp/Signal-Android/blob/main/reproducible-builds/Dockerfile#L30) configuration.
+
## Issues
### Useful bug reports
diff --git a/app/build.gradle b/app/build.gradle
index 2de1d60d57..65d1238b10 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -520,6 +520,7 @@ dependencies {
implementation project(':sms-exporter')
implementation project(':sticky-header-grid')
implementation project(':photoview')
+ implementation project(':glide-webp')
implementation libs.libsignal.android
diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedBitmapResourceEncoder.java b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedBitmapResourceEncoder.java
index 57c334c4e5..44f6267dc9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedBitmapResourceEncoder.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedBitmapResourceEncoder.java
@@ -60,6 +60,4 @@ public class EncryptedBitmapResourceEncoder extends EncryptedCoder implements Re
return Bitmap.CompressFormat.JPEG;
}
}
-
-
}
diff --git a/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts b/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts
index 54996d0cf6..11d7058a50 100644
--- a/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts
+++ b/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts
@@ -45,6 +45,14 @@ android {
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
}
+
+ buildFeatures {
+ compose = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.4.4"
+ }
}
dependencies {
diff --git a/build.gradle b/build.gradle
index fb87ad09cf..497cd3b363 100644
--- a/build.gradle
+++ b/build.gradle
@@ -98,6 +98,8 @@ task qa {
task clean(type: Delete) {
delete rootProject.buildDir
+ // Because gradle is weird, we delete here for glide-webp/lib project so the clean tasks there doesn't barf
+ delete fileTree("glide-webp/lib/.cxx")
}
task format {
diff --git a/core-util/src/main/java/org/signal/core/util/StreamUtil.java b/core-util/src/main/java/org/signal/core/util/StreamUtil.java
index 7c60e4b8cd..373e1a05ec 100644
--- a/core-util/src/main/java/org/signal/core/util/StreamUtil.java
+++ b/core-util/src/main/java/org/signal/core/util/StreamUtil.java
@@ -60,12 +60,21 @@ public final class StreamUtil {
}
public static byte[] readFully(InputStream in) throws IOException {
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- byte[] buffer = new byte[4096];
- int read;
+ return readFully(in, Integer.MAX_VALUE);
+ }
+
+ public static byte[] readFully(InputStream in, int maxBytes) throws IOException {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ byte[] buffer = new byte[4096];
+ int totalRead = 0;
+ int read;
while ((read = in.read(buffer)) != -1) {
bout.write(buffer, 0, read);
+ totalRead += read;
+ if (totalRead > maxBytes) {
+ throw new IOException("Stream size limit exceeded");
+ }
}
in.close();
diff --git a/glide-config/build.gradle b/glide-config/build.gradle
index f5fe3d458f..64464b0ce5 100644
--- a/glide-config/build.gradle
+++ b/glide-config/build.gradle
@@ -10,4 +10,6 @@ android {
dependencies {
implementation libs.glide.glide
kapt libs.glide.compiler
+
+ implementation project(':glide-webp')
}
diff --git a/glide-webp/app/.gitignore b/glide-webp/app/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/glide-webp/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/glide-webp/app/build.gradle.kts b/glide-webp/app/build.gradle.kts
new file mode 100644
index 0000000000..24c08f3c0e
--- /dev/null
+++ b/glide-webp/app/build.gradle.kts
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+plugins {
+ id("signal-sample-app")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "org.signal.glide.webp.app"
+}
+
+dependencies {
+ implementation(project(":glide-webp"))
+
+ implementation(libs.androidx.fragment.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.compose.material3)
+
+ implementation(libs.glide.glide)
+ kapt(libs.glide.compiler)
+}
diff --git a/glide-webp/app/src/main/AndroidManifest.xml b/glide-webp/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..ad55630ca3
--- /dev/null
+++ b/glide-webp/app/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/glide-webp/app/src/main/assets/test_01.webp b/glide-webp/app/src/main/assets/test_01.webp
new file mode 100644
index 0000000000..122741b605
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_01.webp differ
diff --git a/glide-webp/app/src/main/assets/test_02.webp b/glide-webp/app/src/main/assets/test_02.webp
new file mode 100644
index 0000000000..c833c5d4af
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_02.webp differ
diff --git a/glide-webp/app/src/main/assets/test_03.webp b/glide-webp/app/src/main/assets/test_03.webp
new file mode 100644
index 0000000000..e1cf658373
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_03.webp differ
diff --git a/glide-webp/app/src/main/assets/test_04.webp b/glide-webp/app/src/main/assets/test_04.webp
new file mode 100644
index 0000000000..a608fc85c2
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_04.webp differ
diff --git a/glide-webp/app/src/main/assets/test_05.webp b/glide-webp/app/src/main/assets/test_05.webp
new file mode 100644
index 0000000000..89e6075fde
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_05.webp differ
diff --git a/glide-webp/app/src/main/assets/test_06_lossless.webp b/glide-webp/app/src/main/assets/test_06_lossless.webp
new file mode 100644
index 0000000000..1953c59b3d
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_06_lossless.webp differ
diff --git a/glide-webp/app/src/main/assets/test_06_lossy.webp b/glide-webp/app/src/main/assets/test_06_lossy.webp
new file mode 100644
index 0000000000..fd78f9335a
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_06_lossy.webp differ
diff --git a/glide-webp/app/src/main/assets/test_07_lossless.webp b/glide-webp/app/src/main/assets/test_07_lossless.webp
new file mode 100644
index 0000000000..7c5de80339
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_07_lossless.webp differ
diff --git a/glide-webp/app/src/main/assets/test_07_lossy.webp b/glide-webp/app/src/main/assets/test_07_lossy.webp
new file mode 100644
index 0000000000..0a8560a772
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_07_lossy.webp differ
diff --git a/glide-webp/app/src/main/assets/test_08_lossless.webp b/glide-webp/app/src/main/assets/test_08_lossless.webp
new file mode 100644
index 0000000000..32ba54bca6
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_08_lossless.webp differ
diff --git a/glide-webp/app/src/main/assets/test_08_lossy.webp b/glide-webp/app/src/main/assets/test_08_lossy.webp
new file mode 100644
index 0000000000..b87117bf2f
Binary files /dev/null and b/glide-webp/app/src/main/assets/test_08_lossy.webp differ
diff --git a/glide-webp/app/src/main/java/org/signal/glide/webp/app/MainActivity.kt b/glide-webp/app/src/main/java/org/signal/glide/webp/app/MainActivity.kt
new file mode 100644
index 0000000000..bc39bc65c0
--- /dev/null
+++ b/glide-webp/app/src/main/java/org/signal/glide/webp/app/MainActivity.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.signal.glide.webp.app
+
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.bumptech.glide.Glide
+import com.bumptech.glide.GlideBuilder
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import org.signal.core.util.dp
+
+/**
+ * Main activity for this sample app.
+ */
+class MainActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.main_activitiy)
+
+ Glide.init(
+ this,
+ GlideBuilder()
+ .setLogLevel(Log.VERBOSE)
+ )
+
+ val context = this
+
+ findViewById(R.id.list).apply {
+ adapter = ImageAdapter()
+ layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
+ }
+ }
+
+ class ImageAdapter : RecyclerView.Adapter() {
+
+ private val data: List = listOf(
+ "test_01.webp",
+ "test_02.webp",
+ "test_03.webp",
+ "test_04.webp",
+ "test_05.webp",
+ "test_06_lossless.webp",
+ "test_06_lossy.webp",
+ "test_07_lossless.webp"
+ )
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
+ return ImageViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.image_item, parent, false))
+ }
+
+ override fun getItemCount(): Int {
+ return data.size
+ }
+
+ override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
+ holder.bind(data[position])
+ }
+ }
+
+ class ImageViewHolder(itemView: View) : ViewHolder(itemView) {
+ val image: ImageView by lazy { itemView.findViewById(R.id.image) }
+
+ fun bind(filename: String) {
+ Glide.with(itemView)
+ .load(Uri.parse("file:///android_asset/$filename"))
+ .skipMemoryCache(true)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .override(250.dp)
+ .into(image)
+ }
+ }
+}
diff --git a/glide-webp/app/src/main/java/org/signal/glide/webp/app/SampleAppGlideModule.kt b/glide-webp/app/src/main/java/org/signal/glide/webp/app/SampleAppGlideModule.kt
new file mode 100644
index 0000000000..56420b5b2b
--- /dev/null
+++ b/glide-webp/app/src/main/java/org/signal/glide/webp/app/SampleAppGlideModule.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.signal.glide.webp.app
+
+import android.content.Context
+import com.bumptech.glide.Glide
+import com.bumptech.glide.Registry
+import com.bumptech.glide.annotation.GlideModule
+import com.bumptech.glide.module.AppGlideModule
+import org.signal.core.util.logging.Log
+
+@GlideModule
+class SampleAppGlideModule : AppGlideModule() {
+ override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
+ Log.e("SPIDERMAN", "AppModule - registerComponents")
+ }
+}
diff --git a/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Color.kt b/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Color.kt
new file mode 100644
index 0000000000..bb54725391
--- /dev/null
+++ b/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Color.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.signal.glide.webp.app.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
diff --git a/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Theme.kt b/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Theme.kt
new file mode 100644
index 0000000000..31cca5ab2a
--- /dev/null
+++ b/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Theme.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.signal.glide.webp.app.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+
+private val DarkColorScheme = darkColorScheme(
+ primary = Purple80,
+ secondary = PurpleGrey80,
+ tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+ primary = Purple40,
+ secondary = PurpleGrey40,
+ tertiary = Pink40
+
+ /* Other default colors to override
+ background = Color(0xFFFFFBFE),
+ surface = Color(0xFFFFFBFE),
+ onPrimary = Color.White,
+ onSecondary = Color.White,
+ onTertiary = Color.White,
+ onBackground = Color(0xFF1C1B1F),
+ onSurface = Color(0xFF1C1B1F),
+ */
+)
+
+@Composable
+fun SignalTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.primary.toArgb()
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+ }
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+}
diff --git a/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Type.kt b/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Type.kt
new file mode 100644
index 0000000000..fc46389f3d
--- /dev/null
+++ b/glide-webp/app/src/main/java/org/signal/glide/webp/app/theme/Type.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.signal.glide.webp.app.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+ /* Other default text styles to override
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+ */
+)
diff --git a/glide-webp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/glide-webp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..743651fa54
--- /dev/null
+++ b/glide-webp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/glide-webp/app/src/main/res/drawable/ic_launcher_background.xml b/glide-webp/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000..126c9713e0
--- /dev/null
+++ b/glide-webp/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/glide-webp/app/src/main/res/layout/image_item.xml b/glide-webp/app/src/main/res/layout/image_item.xml
new file mode 100644
index 0000000000..7855954f96
--- /dev/null
+++ b/glide-webp/app/src/main/res/layout/image_item.xml
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/glide-webp/app/src/main/res/layout/main_activitiy.xml b/glide-webp/app/src/main/res/layout/main_activitiy.xml
new file mode 100644
index 0000000000..14c14e9249
--- /dev/null
+++ b/glide-webp/app/src/main/res/layout/main_activitiy.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/glide-webp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/glide-webp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..80e53a5e47
--- /dev/null
+++ b/glide-webp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/glide-webp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/glide-webp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000..80e53a5e47
--- /dev/null
+++ b/glide-webp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/glide-webp/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/glide-webp/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000000..c209e78ecd
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/glide-webp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..b2dfe3d1ba
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/glide-webp/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000000..4f0f1d64e5
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/glide-webp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..62b611da08
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/glide-webp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..948a3070fe
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/glide-webp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..1b9a6956b3
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/glide-webp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..28d4b77f9f
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/glide-webp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..9287f50836
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/glide-webp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..aa7d6427e6
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/glide-webp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/glide-webp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..9126ae37cb
Binary files /dev/null and b/glide-webp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/glide-webp/app/src/main/res/values/colors.xml b/glide-webp/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000000..38279fc979
--- /dev/null
+++ b/glide-webp/app/src/main/res/values/colors.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/glide-webp/app/src/main/res/values/strings.xml b/glide-webp/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..1ed18d6e79
--- /dev/null
+++ b/glide-webp/app/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+
+ Glide WebP
+
\ No newline at end of file
diff --git a/glide-webp/app/src/main/res/values/themes.xml b/glide-webp/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000000..67d43ba29d
--- /dev/null
+++ b/glide-webp/app/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/glide-webp/lib/.gitignore b/glide-webp/lib/.gitignore
new file mode 100644
index 0000000000..c73c14ab99
--- /dev/null
+++ b/glide-webp/lib/.gitignore
@@ -0,0 +1,2 @@
+/build
+.cxx
diff --git a/glide-webp/lib/build.gradle.kts b/glide-webp/lib/build.gradle.kts
new file mode 100644
index 0000000000..3c660a5f70
--- /dev/null
+++ b/glide-webp/lib/build.gradle.kts
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+plugins {
+ id("signal-library")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "org.signal.glide.webp"
+
+ defaultConfig {
+ externalNativeBuild {
+ cmake {
+ cppFlags("-std=c++17", "-fvisibility=hidden")
+ arguments("-DLIBWEBP_PATH=$rootDir/libwebp")
+ }
+ }
+ }
+
+ externalNativeBuild {
+ cmake {
+ path = file("$projectDir/src/main/cpp/CMakeLists.txt")
+ version = "3.22.1"
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":core-util"))
+
+ implementation(libs.glide.glide)
+ kapt(libs.glide.compiler)
+}
diff --git a/glide-webp/lib/src/main/cpp/CMakeLists.txt b/glide-webp/lib/src/main/cpp/CMakeLists.txt
new file mode 100755
index 0000000000..496edd0980
--- /dev/null
+++ b/glide-webp/lib/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,24 @@
+cmake_minimum_required(VERSION 3.22.1)
+
+project(signal-webp)
+
+add_compile_options(-Oz)
+set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
+
+set(BUILD_SHARED_LIBS ON)
+set(WEBP_BUILD_ANIM_UTILS OFF)
+set(WEBP_BUILD_CWEBP OFF)
+set(WEBP_BUILD_DWEBP OFF)
+set(WEBP_BUILD_VWEBP OFF)
+set(WEBP_BUILD_WEBPINFO OFF)
+set(WEBP_BUILD_LIBWEBPMUX OFF)
+set(WEBP_BUILD_EXTRAS OFF)
+set(WEBP_NEAR_LOSSLESS, OFF)
+
+file(GLOB SOURCES ${CMAKE_SOURCE_DIR}/*.cpp)
+
+add_subdirectory(${LIBWEBP_PATH} ${CMAKE_CURRENT_BINARY_DIR}/libwebp)
+
+add_library(signalwebp SHARED ${SOURCES})
+target_include_directories(signalwebp PRIVATE ${LIBWEBP_PATH}/src)
+target_link_libraries(signalwebp webpdemux)
diff --git a/glide-webp/lib/src/main/cpp/signalwebp.cpp b/glide-webp/lib/src/main/cpp/signalwebp.cpp
new file mode 100644
index 0000000000..a2702229a1
--- /dev/null
+++ b/glide-webp/lib/src/main/cpp/signalwebp.cpp
@@ -0,0 +1,62 @@
+#include
+#include
+
+jobject createBitmap(JNIEnv *env, int width, int height, const uint8_t *pixels) {
+ static auto jbitmapConfigClass = reinterpret_cast(env->NewGlobalRef(env->FindClass("android/graphics/Bitmap$Config")));
+ static jfieldID jbitmapConfigARGB8888Field = env->GetStaticFieldID(jbitmapConfigClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
+ static auto jbitmapClass = reinterpret_cast(env->NewGlobalRef(env->FindClass("android/graphics/Bitmap")));
+ static jmethodID jbitmapCreateBitmapMethod = env->GetStaticMethodID(jbitmapClass, "createBitmap", "([IIIIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
+
+ jintArray intArray = env->NewIntArray(width * height);
+ env->SetIntArrayRegion(intArray, 0, width * height, reinterpret_cast(pixels));
+
+ jobject argb8888Config = env->GetStaticObjectField(jbitmapConfigClass, jbitmapConfigARGB8888Field);
+ jobject jbitmap = env->CallStaticObjectMethod(jbitmapClass, jbitmapCreateBitmapMethod, intArray, 0, width, width, height, argb8888Config);
+ env->DeleteLocalRef(argb8888Config);
+
+ return jbitmap;
+}
+
+jobject nativeDecodeBitmap(JNIEnv *env, jobject, jbyteArray data) {
+ jbyte *javaBytes = env->GetByteArrayElements(data, nullptr);
+ auto *buffer = reinterpret_cast(javaBytes);
+ jsize bufferLength = env->GetArrayLength(data);
+
+ WebPBitstreamFeatures features;
+ WebPGetFeatures(buffer, bufferLength, &features);
+
+ int width;
+ int height;
+
+ uint8_t *pixels = WebPDecodeBGRA(buffer, bufferLength, &width, &height);
+ jobject jbitmap = createBitmap(env, width, height, pixels);
+
+ WebPFree(pixels);
+ env->ReleaseByteArrayElements(data, javaBytes, 0);
+
+ return jbitmap;
+}
+
+JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return JNI_ERR;
+ }
+
+ jclass c = env->FindClass("org/signal/glide/webp/WebpDecoder");
+ if (c == nullptr) {
+ return JNI_ERR;
+ }
+
+ static const JNINativeMethod methods[] = {
+ {"nativeDecodeBitmap", "([B)Landroid/graphics/Bitmap;", reinterpret_cast(nativeDecodeBitmap)}
+ };
+
+ int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
+
+ if (rc != JNI_OK) {
+ return rc;
+ }
+
+ return JNI_VERSION_1_6;
+}
diff --git a/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpDecoder.kt b/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpDecoder.kt
new file mode 100644
index 0000000000..a6de32c7df
--- /dev/null
+++ b/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpDecoder.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.signal.glide.webp
+
+import android.graphics.Bitmap
+
+class WebpDecoder {
+
+ init {
+ System.loadLibrary("signalwebp")
+ }
+
+ external fun nativeDecodeBitmap(data: ByteArray): Bitmap?
+}
diff --git a/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpInputStreamResourceDecoder.kt b/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpInputStreamResourceDecoder.kt
new file mode 100644
index 0000000000..194f9593b8
--- /dev/null
+++ b/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpInputStreamResourceDecoder.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.signal.glide.webp
+
+import android.graphics.Bitmap
+import com.bumptech.glide.load.Options
+import com.bumptech.glide.load.ResourceDecoder
+import com.bumptech.glide.load.engine.Resource
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
+import com.bumptech.glide.load.resource.bitmap.BitmapResource
+import org.signal.core.util.StreamUtil
+import org.signal.core.util.logging.Log
+import java.io.IOException
+import java.io.InputStream
+
+class WebpInputStreamResourceDecoder(private val bitmapPool: BitmapPool) : ResourceDecoder {
+
+ companion object {
+ private const val TAG = "WebpResourceDecoder" // Name > 23 characters
+
+ private val MAGIC_NUMBER_P1 = byteArrayOf(0x52, 0x49, 0x46, 0x46) // "RIFF"
+ private val MAGIC_NUMBER_P2 = byteArrayOf(0x57, 0x45, 0x42, 0x50) // "WEBP"
+
+ private const val MAX_WEBP_COMPRESSED_SIZE = 10 * 1024 * 1024; // 10mb
+ }
+
+ /**
+ * The "magic number" for a WEBP file is in the first 12 bytes. The layout is:
+ *
+ * [0-3]: "RIFF"
+ * [4-7]: File length
+ * [8-11]: "WEBP"
+ *
+ * We're not verifying the file length here, so we just need to check the first and last
+ */
+ override fun handles(source: InputStream, options: Options): Boolean {
+ return try {
+ val magicNumberP1 = ByteArray(4)
+ StreamUtil.readFully(source, magicNumberP1)
+
+ val fileLength = ByteArray(4)
+ StreamUtil.readFully(source, fileLength)
+
+ val magicNumberP2 = ByteArray(4)
+ StreamUtil.readFully(source, magicNumberP2)
+
+ magicNumberP1.contentEquals(MAGIC_NUMBER_P1) && magicNumberP2.contentEquals(MAGIC_NUMBER_P2)
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to read magic number from stream!", e)
+ false
+ }
+ }
+
+ override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource? {
+ Log.d(TAG, "decode()")
+
+ val webp: ByteArray = try {
+ StreamUtil.readFully(source, MAX_WEBP_COMPRESSED_SIZE)
+ } catch (e: IOException) {
+ Log.w(TAG, "Unexpected IOException hit while reading image data", e)
+ throw e
+ }
+
+
+ val bitmap: Bitmap? = WebpDecoder().nativeDecodeBitmap(webp)
+ return BitmapResource.obtain(bitmap, bitmapPool)
+ }
+}
diff --git a/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpLibraryGlideModule.kt b/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpLibraryGlideModule.kt
new file mode 100644
index 0000000000..eac61473f3
--- /dev/null
+++ b/glide-webp/lib/src/main/java/org/signal/glide/webp/WebpLibraryGlideModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.signal.glide.webp
+
+import android.content.Context
+import android.graphics.Bitmap
+import com.bumptech.glide.Glide
+import com.bumptech.glide.Registry
+import com.bumptech.glide.annotation.GlideModule
+import com.bumptech.glide.module.LibraryGlideModule
+import org.signal.core.util.logging.Log
+import java.io.InputStream
+
+/**
+ * Registers a custom handler for webp images.
+ */
+@GlideModule
+class WebpLibraryGlideModule : LibraryGlideModule() {
+ companion object {
+ private val TAG = Log.tag(WebpLibraryGlideModule::class.java)
+ }
+
+ override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
+ Log.d(TAG, "registerComponents()")
+ registry.prepend(InputStream::class.java, Bitmap::class.java, WebpInputStreamResourceDecoder(glide.bitmapPool))
+ }
+}
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 127f3003dd..71bcc397d7 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -426,6 +426,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
diff --git a/libwebp b/libwebp
new file mode 160000
index 0000000000..ca332209cb
--- /dev/null
+++ b/libwebp
@@ -0,0 +1 @@
+Subproject commit ca332209cb5567c9b249c86788cb2dbf8847e760
diff --git a/reproducible-builds/Dockerfile b/reproducible-builds/Dockerfile
index 7a2806fab0..80c01db1bd 100644
--- a/reproducible-builds/Dockerfile
+++ b/reproducible-builds/Dockerfile
@@ -27,7 +27,7 @@ RUN unzip ${ANDROID_COMMAND_LINE_TOOLS_FILENAME} -d /usr/local/android-sdk-linux
RUN rm ${ANDROID_COMMAND_LINE_TOOLS_FILENAME}
RUN yes | sdkmanager --update --sdk_root="${ANDROID_HOME}"
-RUN yes | sdkmanager --sdk_root="${ANDROID_HOME}" "platforms;${ANDROID_API_LEVELS}" "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" "extras;google;m2repository" "extras;android;m2repository" "extras;google;google_play_services"
+RUN yes | sdkmanager --sdk_root="${ANDROID_HOME}" "platforms;${ANDROID_API_LEVELS}" "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" "extras;google;m2repository" "extras;android;m2repository" "extras;google;google_play_services" "ndk;25.1.8937393" "cmake;3.22.1"
RUN yes | sdkmanager --licenses --sdk_root="${ANDROID_HOME}"
diff --git a/settings.gradle b/settings.gradle
index 877175f8f0..f38742db02 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -57,6 +57,8 @@ include ':core-ui'
include ':benchmark'
include ':microbenchmark'
include ':video-app'
+include ':glide-webp'
+include ':glide-webp-app'
project(':app').name = 'Signal-Android'
project(':paging').projectDir = file('paging/lib')
@@ -85,6 +87,9 @@ project(':contacts-app').projectDir = file('contacts/app')
project(':qr').projectDir = file('qr/lib')
project(':qr-app').projectDir = file('qr/app')
+project(':glide-webp').projectDir = file('glide-webp/lib')
+project(':glide-webp-app').projectDir = file('glide-webp/app')
+
rootProject.name='Signal'
-apply from: 'dependencies.gradle'
\ No newline at end of file
+apply from: 'dependencies.gradle'