Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -518,8 +518,12 @@ object FileSystem {
fun getLogs(zipFd: ParcelFileDescriptor) {
runCatching {
ZipOutputStream(java.io.FileOutputStream(zipFd.fileDescriptor)).use { os ->
// The commit, not just the version code: the code is the commit count on master, so
// every branch build at the same depth wears the number of an official build it was
// never made from. Without it an attached archive cannot be tied to a binary.
val comment =
"Vector ${BuildConfig.BUILD_TYPE} ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
"Vector ${BuildConfig.BUILD_TYPE} ${BuildConfig.VERSION_NAME} " +
"(${BuildConfig.VERSION_CODE}) ${BuildConfig.VERSION_HASH}"
os.setComment(comment)
os.setLevel(java.util.zip.Deflater.BEST_COMPRESSION)

Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }

[libraries]
# Build-only: reads the optimised DEX in checkXResourcesIsolation.
smali-dexlib2 = { group = "com.android.tools.smali", name = "smali-dexlib2", version = "3.0.9" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.19.0" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
Expand Down
11 changes: 10 additions & 1 deletion legacy/consumer-rules.pro
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
# Keeping a class also keeps the optimiser from merging it into another one, so the resource types
# below stay classes of their own. That says nothing about the classes the optimiser *invents*: a
# lambda written anywhere becomes a class, and classes of the same shape are merged afterwards, so a
# reference can end up in a class no one wrote and no rule here names. Types whose super class is
# generated at runtime must not travel that way, since they cannot be resolved until the device has
# built the super class and the runtime never retries a failed resolution. That invariant is checked
# against the optimised dex by checkXResourcesIsolationRelease, not from this file.
-keep class android.** { *; }
-keep class de.robv.android.xposed.** { *; }

# Workaround to bypass verification of in-memory built class xposed.dummy.XResourcesSuperClass
# The in-memory built class xposed.dummy.XResourcesSuperClass exists only on the device, so the
# class below is a deliberate split: it isolates the reference to XResources from its owner, which
# would otherwise be verified long before that super class exists.
-keepclassmembers class org.matrix.vector.legacy.LegacyDelegateImpl$ResourceProxy { *; }
53 changes: 48 additions & 5 deletions legacy/src/main/java/android/content/res/XResources.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,7 +75,8 @@ public class XResources extends XResourcesSuperClass {
private static final WeakHashMap<XmlResourceParser, XMLInstanceDetails> sXmlInstanceDetails = new WeakHashMap<>();

private static final String EXTRA_XML_INSTANCE_DETAILS = "xmlInstanceDetails";
private static final ThreadLocal<LinkedList<MethodHookParam>> sIncludedLayouts = ThreadLocal.withInitial(() -> new LinkedList<>());
// No lambda, and no anonymous ThreadLocal either. See the note above [includedLayouts].
private static final ThreadLocal<LinkedList<MethodHookParam>> sIncludedLayouts = new ThreadLocal<>();

private static final HashMap<String, Long> sResDirLastModified = new HashMap<>();
private static final HashMap<String, String> sResDirPackageNames = new HashMap<>();
Expand All@@ -92,11 +93,53 @@ public XResources(ClassLoader classLoader, String resDir) {

if (resDir != null) {
synchronized (sReplacementsCacheMap) {
mReplacementsCache = sReplacementsCacheMap.computeIfAbsent(resDir, k -> new byte[128]);
// Not computeIfAbsent: its mapping function would be a lambda, which this class may
// not create. See the note above [includedLayouts]. Under this lock the two spellings
// describe the same operation.
byte[] cache = sReplacementsCacheMap.get(resDir);
if (cache == null) {
cache = new byte[128];
sReplacementsCacheMap.put(resDir, cache);
}
mReplacementsCache = cache;
}
}
}

/**
* The thread's stack of `LayoutInflater.parseInclude` calls, created on first use.
*
* Written the long way on purpose. `ThreadLocal.withInitial(() -> ...)`, and an anonymous
* `ThreadLocal` overriding `initialValue`, would each add a class that names `XResources`, and
* this class may not be named by classes it does not control.
*
* The reason is its super class. {@link xposed.dummy.XResourcesSuperClass} is in no dex; it is
* generated at runtime, because it has to inherit from whichever `Resources` subclass the
* platform actually provides. A type whose super class does not yet exist cannot be resolved,
* and a resolution failure is remembered: the runtime marks the class erroneous and re-throws
* for every later attempt. So the fragility is not local. It travels along references — anything
* naming `XResources` is unusable in the same window, and stays unusable afterwards.
*
* A whole-program optimiser extends that reach in a way the source cannot show. Each lambda
* becomes a class of its own, and classes of the same shape are then merged, so two lambdas
* written in unrelated files can end up as one class. A lambda here is therefore not a private
* detail of this file: it is a reference to `XResources` placed inside a class that arbitrary
* code may instantiate, and every such instantiation inherits the window above.
*
* Keep rules cannot express this — they govern names and members, not which classes an optimiser
* invents. So the rule is stated here, next to what it protects, and checked where it is really
* decided: `checkXResourcesIsolationRelease` reads the optimised dex and fails the build if any
* class outside resource hooking has come to name one of these types.
*/
private static LinkedList<MethodHookParam> includedLayouts() {
LinkedList<MethodHookParam> layouts = sIncludedLayouts.get();
if (layouts == null) {
layouts = new LinkedList<>();
sIncludedLayouts.set(layouts);
}
return layouts;
}

/** Dummy, will never be called (objects are transferred to this class only). */
// private XResources() {
// throw new UnsupportedOperationException();
Expand DownExpand Up@@ -220,12 +263,12 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
final XC_MethodHook parseIncludeHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
sIncludedLayouts.get().push(param);
includedLayouts().push(param);
}

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
sIncludedLayouts.get().pop();
includedLayouts().pop();

if (param.hasThrowable())
return;
Expand DownExpand Up@@ -962,7 +1005,7 @@ public XmlResourceParser getLayout(int id) throws NotFoundException {
sXmlInstanceDetails.put(result, details);

// if we were called inside LayoutInflater.parseInclude, store the details for it
MethodHookParam top = sIncludedLayouts.get().peek();
MethodHookParam top = includedLayouts().peek();
if (top != null)
top.setObjectExtra(EXTRA_XML_INSTANCE_DETAILS, details);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
package org.matrix.vector.manager.data.log

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import org.matrix.vector.manager.BuildConfig

/**
* What a saved bug report is called, wherever it is saved from.
*
* Three places produce one — the log panel, the troubleshooting page, and the root export that
* tars the daemon's folder — and they used to name it three ways. The name is the first thing
* anyone attaching one to an issue sees, and it has to say which build it came from: a report from
* a debug build explains behaviour that a release build does not have, and asking after the fact
* is a round trip that the file name can save.
*
* Not a string resource. It was one, translated into nineteen locales, but a file name is not
* language — a report named in Persian and one named in German are the same file, and the
* translations only made it possible for them to disagree.
*
* [extension] rather than a fixed `zip`: the manager builds a zip through SAF, while the root
* export shells out to `tar`, which is what Android actually ships. Only the extension differs.
*/
fun logArchiveName(extension: String): String =
"Vector-logs-${BuildConfig.BUILD_TYPE}-${LocalDateTime.now().format(ARCHIVE_STAMP)}.$extension"

/**
* Which build wrote an archive, for the archive itself to carry.
*
* The name says the build type and no more, because a name has to stay short enough to read. What
* identifies a *binary* is the commit: the version code is the commit count on master, so every
* branch build at the same depth wears the number of an official build it was never made from.
*
* Where this goes depends on what the format offers. A zip has a comment field and gets this
* verbatim; a backup is our own document and carries it as a field. `tar` has no such slot at all,
* so the root export can only say what its name says.
*/
fun archiveBuildStamp(): String =
"Vector ${BuildConfig.BUILD_TYPE} ${BuildConfig.VERSION_NAME} " +
"(${BuildConfig.VERSION_CODE}) ${BuildConfig.VERSION_HASH}"

/** Sortable, no separators a file manager or a shell would have to be told about. */
private val ARCHIVE_STAMP: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.lsposed.lspd.models.Application
import org.matrix.vector.manager.data.log.archiveBuildStamp
import org.matrix.vector.manager.ipc.DaemonClient
import org.matrix.vector.manager.logE
import org.matrix.vector.manager.logW
Expand All@@ -28,6 +29,10 @@ class BackupRepository(private val context: Context, private val daemon: DaemonC
@Serializable
private data class BackupFile(
val version: Int = FORMAT_VERSION,
// Which build wrote this. gzip's own comment field is not reachable through
// GZIPOutputStream, and this document is ours, so it says so itself -- `zcat file | head`
// answers "where did this come from" without a restore.
val build: String = archiveBuildStamp(),
val createdAt: Long,
val modules: List<BackupModule>,
)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,9 +100,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.launch
import org.matrix.vector.manager.data.log.logArchiveName
import org.matrix.vector.manager.ui.components.VectorAlertDialog
import org.matrix.vector.manager.ui.theme.LocalizedOverlay
import org.matrix.vector.manager.R
Expand DownExpand Up@@ -154,11 +153,8 @@ fun LogsScreen(
) { uri: Uri? ->
if (uri != null) viewModel.saveTo(uri)
}
val fileNameTemplate = stringResource(R.string.logs_save_name)
fun launchSave() {
saveLauncher.launch(
String.format(fileNameTemplate, LocalDateTime.now().format(FILE_STAMP))
)
saveLauncher.launch(logArchiveName("zip"))
}

val savingLabel = stringResource(R.string.logs_saving)
Expand DownExpand Up@@ -1012,8 +1008,6 @@ LocalizedOverlay {
}
}

private val FILE_STAMP: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")

private fun shareZip(context: Context, uri: Uri) {
val intent =
Intent(Intent.ACTION_SEND).apply {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
package org.matrix.vector.manager.ui.screens.report
import android.content.Context
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
Expand DownExpand Up@@ -43,14 +42,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.vector.manager.R
import org.matrix.vector.manager.data.github.GitHubRepository
import org.matrix.vector.manager.data.log.logArchiveName
import org.matrix.vector.manager.di.ServiceLocator
import org.matrix.vector.manager.logE
import org.matrix.vector.manager.ui.components.SnackbarTone
Expand DownExpand Up@@ -184,17 +182,7 @@ fun TroubleshootScreen(
title = stringResource(R.string.report_step_logs),
body = stringResource(R.string.report_step_logs_body),
) {
Button(
onClick = {
saveLauncher.launch(
String.format(
stringResourceOf(context, R.string.logs_save_name),
LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")),
)
)
}
) {
Button(onClick = { saveLauncher.launch(logArchiveName("zip")) }) {
Icon(
Icons.Rounded.Save,
contentDescription = null,
Expand DownExpand Up@@ -320,8 +308,6 @@ private fun Step(
}
}

private fun stringResourceOf(context: Context, id: Int): String = context.getString(id)

/**
* Copies the daemon's log folder somewhere the user can attach it, using root.
*
Expand All@@ -338,8 +324,7 @@ private fun stringResourceOf(context: Context, id: Int): String = context.getStr
* going to be attached to an issue.
*/
private fun exportWithRoot(): Result<String> = runCatching {
val stamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"))
val target = "/sdcard/Download/vector-logs-$stamp.tar.gz"
val target = "/sdcard/Download/${logArchiveName("tar.gz")}"
val process =
ProcessBuilder("su", "-c", "tar -czf $target -C /data/adb/lspd log && chmod 644 $target")
.redirectErrorStream(true)
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-ar/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,7 +69,6 @@
<string name="logs_rotate_failed">رفضت الخدمة بدء سجل جديد</string>
<string name="logs_cancel">إلغاء</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">جارٍ جمع السجلات وملفات tombstone وdmesg…</string>
<string name="logs_saved">حُفظ البلاغ</string>
<string name="logs_save_failed">تعذّر حفظ البلاغ</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-de/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,7 +57,6 @@
<string name="logs_rotate_failed">Der Daemon hat es abgelehnt, ein neues Protokoll zu beginnen</string>
<string name="logs_cancel">Abbrechen</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">Protokolle, Tombstones und dmesg werden gesammelt…</string>
<string name="logs_saved">Fehlerbericht gespeichert</string>
<string name="logs_save_failed">Der Fehlerbericht konnte nicht gespeichert werden</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-es/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,7 +57,6 @@
<string name="logs_rotate_failed">El daemon se negó a empezar un registro nuevo</string>
<string name="logs_cancel">Cancelar</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">Recogiendo registros, tombstones y dmesg…</string>
<string name="logs_saved">Informe de error guardado</string>
<string name="logs_save_failed">No se pudo guardar el informe de error</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-fa/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,7 +57,6 @@
<string name="logs_rotate_failed">سرویس از آغاز گزارش تازه سر باز زد</string>
<string name="logs_cancel">انصراف</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">در حال گردآوری گزارش‌ها، tombstone و dmesg…</string>
<string name="logs_saved">گزارش اشکال ذخیره شد</string>
<string name="logs_save_failed">ذخیرهٔ گزارش اشکال ممکن نشد</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-fr/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,7 +57,6 @@
<string name="logs_rotate_failed">Le démon a refusé de commencer un nouveau journal</string>
<string name="logs_cancel">Annuler</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">Collecte des journaux, des tombstones et de dmesg…</string>
<string name="logs_saved">Rapport de bug enregistré</string>
<string name="logs_save_failed">Impossible d\'enregistrer le rapport de bug</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-in/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,7 +54,6 @@
<string name="logs_rotate_failed">Daemon menolak memulai log baru</string>
<string name="logs_cancel">Batal</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">Mengumpulkan log, tombstone, dan dmesg…</string>
<string name="logs_saved">Laporan bug tersimpan</string>
<string name="logs_save_failed">Laporan bug tidak bisa disimpan</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-it/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,7 +57,6 @@
<string name="logs_rotate_failed">Il daemon si è rifiutato di cominciare un nuovo log</string>
<string name="logs_cancel">Annulla</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">Raccolta di log, tombstone e dmesg…</string>
<string name="logs_saved">Segnalazione di bug salvata</string>
<string name="logs_save_failed">Impossibile salvare la segnalazione di bug</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-iw/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,7 +63,6 @@
<string name="logs_rotate_failed">השירות סירב להתחיל יומן חדש</string>
<string name="logs_cancel">ביטול</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">אוסף יומנים, קובצי tombstone ו-dmesg…</string>
<string name="logs_saved">דיווח התקלה נשמר</string>
<string name="logs_save_failed">לא ניתן היה לשמור את דיווח התקלה</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-ja/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,7 +54,6 @@
<string name="logs_rotate_failed">デーモンが新しいログの開始を拒否しました</string>
<string name="logs_cancel">キャンセル</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">ログ・tombstone・dmesg を集めています…</string>
<string name="logs_saved">不具合報告を保存しました</string>
<string name="logs_save_failed">不具合報告を保存できませんでした</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-ko/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,7 +54,6 @@
<string name="logs_rotate_failed">데몬이 새 로그 시작을 거부했습니다</string>
<string name="logs_cancel">취소</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">로그, tombstone, dmesg를 모으는 중…</string>
<string name="logs_saved">버그 신고를 저장했습니다</string>
<string name="logs_save_failed">버그 신고를 저장하지 못했습니다</string>
Expand Down
1 change: 0 additions & 1 deletion manager/src/main/res/values-pl/strings_logs.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,7 +63,6 @@
<string name="logs_rotate_failed">Usługa odmówiła rozpoczęcia nowego dziennika</string>
<string name="logs_cancel">Anuluj</string>

<string name="logs_save_name">vector-logs-%1$s.zip</string>
<string name="logs_saving">Zbieranie dzienników, tombstone\'ów i dmesg…</string>
<string name="logs_saved">Zapisano zgłoszenie błędu</string>
<string name="logs_save_failed">Nie udało się zapisać zgłoszenia błędu</string>
Expand Down
Loading
Loading