That fuck shit the fascists are using
1/*
2 * Copyright 2023 Signal Messenger, LLC
3 * SPDX-License-Identifier: AGPL-3.0-only
4 */
5
6package org.tm.archive.providers
7
8import android.content.ContentUris
9import android.content.ContentValues
10import android.content.Intent
11import android.content.UriMatcher
12import android.database.Cursor
13import android.graphics.Bitmap
14import android.net.Uri
15import android.os.ParcelFileDescriptor
16import org.signal.core.util.concurrent.SignalExecutors
17import org.signal.core.util.logging.Log
18import org.tm.archive.BuildConfig
19import org.tm.archive.database.SignalDatabase
20import org.tm.archive.profiles.AvatarHelper
21import org.tm.archive.recipients.Recipient
22import org.tm.archive.recipients.RecipientId
23import org.tm.archive.service.KeyCachingService
24import org.tm.archive.util.AvatarUtil
25import org.tm.archive.util.DrawableUtil
26import org.tm.archive.util.MediaUtil
27import java.io.File
28import java.io.FileNotFoundException
29import java.io.IOException
30
31/**
32 * Provides user avatar bitmaps to the android system service for use in notifications and shortcuts.
33 *
34 * This file heavily borrows from [PartProvider]
35 */
36class AvatarProvider : BaseContentProvider() {
37
38 companion object {
39 private val TAG = Log.tag(AvatarProvider::class.java)
40 private const val CONTENT_AUTHORITY = "${BuildConfig.APPLICATION_ID}.avatar"
41 private const val CONTENT_URI_STRING = "content://$CONTENT_AUTHORITY/avatar"
42 private const val AVATAR = 1
43 private val CONTENT_URI = Uri.parse(CONTENT_URI_STRING)
44 private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
45 addURI(CONTENT_AUTHORITY, "avatar/#", AVATAR)
46 }
47
48 private const val VERBOSE = false
49
50 @JvmStatic
51 fun getContentUri(recipientId: RecipientId): Uri {
52 if (VERBOSE) Log.d(TAG, "getContentUri: $recipientId")
53 return ContentUris.withAppendedId(CONTENT_URI, recipientId.toLong())
54 }
55 }
56
57 override fun onCreate(): Boolean {
58 if (VERBOSE) Log.i(TAG, "onCreate called")
59 return true
60 }
61
62 @Throws(FileNotFoundException::class)
63 override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
64 if (VERBOSE) Log.i(TAG, "openFile() called!")
65
66 if (KeyCachingService.isLocked(context)) {
67 Log.w(TAG, "masterSecret was null, abandoning.")
68 return null
69 }
70
71 if (SignalDatabase.instance == null) {
72 Log.w(TAG, "SignalDatabase unavailable")
73 return null
74 }
75
76 if (uriMatcher.match(uri) == AVATAR) {
77 if (VERBOSE) Log.i(TAG, "Loading avatar.")
78 try {
79 val recipient = getRecipientId(uri)?.let { Recipient.resolved(it) } ?: return null
80 return getParcelFileDescriptorForAvatar(recipient)
81 } catch (ioe: IOException) {
82 Log.w(TAG, ioe)
83 throw FileNotFoundException("Error opening file: " + ioe.message)
84 }
85 }
86
87 Log.w(TAG, "Bad request.")
88 throw FileNotFoundException("Request for bad avatar.")
89 }
90
91 override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
92 if (VERBOSE) Log.i(TAG, "query() called: $uri")
93
94 if (SignalDatabase.instance == null) {
95 Log.w(TAG, "SignalDatabase unavailable")
96 return null
97 }
98
99 if (uriMatcher.match(uri) == AVATAR) {
100 val recipientId = getRecipientId(uri) ?: return null
101
102 if (AvatarHelper.hasAvatar(context!!, recipientId)) {
103 val file: File = AvatarHelper.getAvatarFile(context!!, recipientId)
104 if (file.exists()) {
105 return createCursor(projection, file.name, file.length())
106 }
107 }
108
109 return createCursor(projection, "fallback-$recipientId.jpg", 0)
110 } else {
111 return null
112 }
113 }
114
115 override fun getType(uri: Uri): String? {
116 if (VERBOSE) Log.i(TAG, "getType() called: $uri")
117
118 if (SignalDatabase.instance == null) {
119 Log.w(TAG, "SignalDatabase unavailable")
120 return null
121 }
122
123 if (uriMatcher.match(uri) == AVATAR) {
124 getRecipientId(uri) ?: return null
125
126 return MediaUtil.IMAGE_PNG
127 }
128
129 return null
130 }
131
132 override fun insert(uri: Uri, values: ContentValues?): Uri? {
133 if (VERBOSE) Log.i(TAG, "insert() called")
134 return null
135 }
136
137 override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
138 if (VERBOSE) Log.i(TAG, "delete() called")
139 context?.applicationContext?.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
140 return 0
141 }
142
143 override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
144 if (VERBOSE) Log.i(TAG, "update() called")
145 return 0
146 }
147
148 private fun getRecipientId(uri: Uri): RecipientId? {
149 val rawRecipientId = ContentUris.parseId(uri)
150 if (rawRecipientId <= 0) {
151 Log.w(TAG, "Invalid recipient id.")
152 return null
153 }
154
155 val recipientId = RecipientId.from(rawRecipientId)
156 if (!SignalDatabase.recipients.containsId(recipientId)) {
157 Log.w(TAG, "Recipient does not exist.")
158 return null
159 }
160
161 return recipientId
162 }
163
164 private fun getParcelFileDescriptorForAvatar(recipient: Recipient): ParcelFileDescriptor {
165 val pipe: Array<ParcelFileDescriptor> = ParcelFileDescriptor.createPipe()
166
167 SignalExecutors.UNBOUNDED.execute {
168 ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]).use { output ->
169 if (VERBOSE) Log.i(TAG, "Writing to pipe:${recipient.id}")
170
171 AvatarUtil.getBitmapForNotification(context!!, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE).apply {
172 compress(Bitmap.CompressFormat.PNG, 100, output)
173 }
174 output.flush()
175 if (VERBOSE) Log.i(TAG, "Writing to pipe done:${recipient.id}")
176 }
177 }
178
179 return pipe[0]
180 }
181}