Skip to content

Commit 7fbb8a6

Browse files
committed
release: prepare MG4 ABRP Uploader 1.0.2
1 parent f4afd14 commit 7fbb8a6

14 files changed

Lines changed: 365 additions & 49 deletions

File tree

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ anything inside the service effectively cannot be tested without a car.
5252
|---|---|---|
5353
| `main` | everything shared | all builds |
5454
| `stable` | `UpdateHook` no-op | stable channel |
55-
| `unstable` | `OtaUpdater`, `ApkSignature`, `UpdateHook` | unstable channel |
55+
| `unstable` | automatic private-cache OTA (`OtaUpdater`, `ApkSignature`, `UpdateHook`) | unstable channel |
5656
| `debug` | `VhalProbe` — enumerates the whole VHAL | debug builds only |
5757
| `testUnstable` | OTA policy tests | test only |
5858

‎CHANGELOG.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to this project are documented here. Format follows
44
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
55
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [1.0.2] - 2026-08-04
8+
9+
### Added
10+
11+
- Add an in-app About surface with project information and a repository QR code.
12+
13+
### Security
14+
15+
- Keep unstable OTA downloads in private cache, validate every redirect, verify the APK
16+
signing certificate, and require an explicit successful `pm install` result.
17+
- Remove the public-download update path and its storage permission exposure.
18+
719
## [1.0.1] - 2026-08-03
820

921
### Changed

‎DESIGN.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,12 @@ repositories without a rename: `Theme.MG4<App>`, `Widget.MG4.*`, `Text.MG4.*`,
302302
`windowIsFloating=false`, `backgroundDimEnabled=false`. In landscape, a bottom sheet
303303
leaves a third of the height, which means one column and a scrollbar by the fifth entry.
304304
There is nothing behind a picker worth seeing.
305+
- A confirmation or warning with explanatory content is a **dedicated full-screen
306+
window, never a popup**. Put its explicit confirm and cancel actions together at the
307+
upper-right, consistently with the suite's editor windows, keep both at the 72dp touch
308+
minimum, and let the content use all remaining space. Reserve a compact dialog for a
309+
short, single-decision interruption whose complete message remains immediately readable
310+
at 70 cm.
305311

306312
### Lists
307313
- Row height ≥ 72dp, `mg4_surface`, `spacing_sm` gaps. No swipe-to-delete: a gesture with

‎app/build.gradle‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ android {
3434
applicationId "com.mg4.abrptelemetry"
3535
minSdk 27
3636
targetSdk 36
37-
versionCode 2
38-
versionName "1.0.1"
37+
versionCode 3
38+
versionName "1.0.2"
3939

4040
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
4141
}
@@ -134,6 +134,7 @@ dependencies {
134134
implementation libs.activity
135135
implementation libs.constraintlayout
136136
implementation libs.security.crypto
137+
implementation 'com.google.zxing:core:3.5.3'
137138
// android.jar's org.json is a stub in unit tests; the real implementation lets the
138139
// telemetry payload tests parse what the app actually builds.
139140
testImplementation libs.junit

‎app/src/main/java/com/mg4/abrptelemetry/MainActivity.java‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
import android.content.SharedPreferences;
66
import android.content.pm.PackageManager;
77
import android.graphics.Color;
8+
import android.graphics.drawable.ColorDrawable;
9+
import android.net.Uri;
810
import android.os.Bundle;
911
import android.view.View;
1012
import android.widget.AdapterView;
1113
import android.widget.ArrayAdapter;
1214
import android.widget.Button;
15+
import android.widget.ImageView;
1316
import android.widget.Spinner;
1417
import android.widget.TextView;
1518

@@ -21,13 +24,16 @@
2124
import androidx.core.content.ContextCompat;
2225

2326
import com.google.android.material.switchmaterial.SwitchMaterial;
27+
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
28+
import com.google.android.material.button.MaterialButton;
2429
import com.google.android.material.textfield.TextInputEditText;
2530
import com.google.android.material.textfield.TextInputLayout;
2631

2732

2833
public class MainActivity extends AppCompatActivity {
2934

3035
private static final int LOCATION_PERMISSION_REQUEST = 100;
36+
private static final String REPOSITORY_URL = "https://github.com/malys/MG4AbrpUploader";
3137

3238
// Status colours come from the palette, not from the Material swatches: #4CAF50 and
3339
// #F44336 sit around 4:1 on this background, which disappears behind a sunlit
@@ -114,6 +120,7 @@ protected void onCreate(Bundle savedInstanceState) {
114120
logPane.setVisibility(checkedId == R.id.tabLog ? View.VISIBLE : View.GONE);
115121
});
116122
tabGroup.check(R.id.tabAbrp);
123+
findViewById(R.id.about_button).setOnClickListener(v -> showAbout());
117124

118125
bindCadenceControls();
119126

@@ -166,6 +173,31 @@ protected void onCreate(Bundle savedInstanceState) {
166173
});
167174
}
168175

176+
private void showAbout() {
177+
String version;
178+
try {
179+
version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
180+
} catch (PackageManager.NameNotFoundException e) {
181+
version = getString(R.string.about_version_unknown);
182+
}
183+
View content = getLayoutInflater().inflate(R.layout.dialog_about, null);
184+
content.<TextView>findViewById(R.id.about_version)
185+
.setText(getString(R.string.about_version, version));
186+
ImageView qr = content.findViewById(R.id.about_qr_code);
187+
android.graphics.Bitmap bitmap = QrCode.generate(REPOSITORY_URL, 416);
188+
if (bitmap != null) qr.setImageBitmap(bitmap);
189+
content.findViewById(R.id.about_repository).setOnClickListener(v ->
190+
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(REPOSITORY_URL))));
191+
androidx.appcompat.app.AlertDialog dialog = new MaterialAlertDialogBuilder(this)
192+
.setView(content)
193+
.create();
194+
content.<MaterialButton>findViewById(R.id.about_close).setOnClickListener(v -> dialog.dismiss());
195+
dialog.show();
196+
if (dialog.getWindow() != null) {
197+
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
198+
}
199+
}
200+
169201
@Override
170202
protected void onResume() {
171203
super.onResume();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.mg4.abrptelemetry;
2+
3+
import android.graphics.Bitmap;
4+
import android.graphics.Color;
5+
6+
import com.google.zxing.BarcodeFormat;
7+
import com.google.zxing.EncodeHintType;
8+
import com.google.zxing.MultiFormatWriter;
9+
import com.google.zxing.common.BitMatrix;
10+
11+
import java.util.EnumMap;
12+
import java.util.Map;
13+
14+
/** Generates the repository QR locally; no network capability is involved. */
15+
final class QrCode {
16+
private QrCode() {}
17+
18+
static Bitmap generate(String content, int sizePx) {
19+
try {
20+
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
21+
hints.put(EncodeHintType.MARGIN, 1);
22+
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
23+
BitMatrix matrix = new MultiFormatWriter().encode(
24+
content, BarcodeFormat.QR_CODE, sizePx, sizePx, hints);
25+
Bitmap bitmap = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.RGB_565);
26+
for (int x = 0; x < sizePx; x++) {
27+
for (int y = 0; y < sizePx; y++) {
28+
bitmap.setPixel(x, y, matrix.get(x, y) ? Color.BLACK : Color.WHITE);
29+
}
30+
}
31+
return bitmap;
32+
} catch (Exception ignored) {
33+
return null;
34+
}
35+
}
36+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
3+
<solid android:color="@color/mg4_icon_tile" />
4+
<corners android:radius="@dimen/mg4_corner" />
5+
</shape>
6+

‎app/src/main/res/layout/activity_main.xml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@
5252
android:layout_height="wrap_content"
5353
android:text="@string/tab_log" />
5454
</com.google.android.material.button.MaterialButtonToggleGroup>
55+
56+
<Space
57+
android:layout_width="0dp"
58+
android:layout_height="1dp"
59+
android:layout_weight="1" />
60+
61+
<com.google.android.material.button.MaterialButton
62+
android:id="@+id/about_button"
63+
style="@style/Widget.MG4.Button.Outlined"
64+
android:layout_width="wrap_content"
65+
android:layout_height="wrap_content"
66+
android:text="@string/about_action" />
5567
</LinearLayout>
5668

5769
<FrameLayout
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<com.google.android.material.card.MaterialCardView
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:app="http://schemas.android.com/apk/res-auto"
5+
android:layout_width="760dp"
6+
android:layout_height="wrap_content"
7+
app:cardBackgroundColor="@color/mg4_surface"
8+
app:cardCornerRadius="@dimen/mg4_corner"
9+
app:cardElevation="0dp"
10+
app:strokeColor="@color/mg4_outline"
11+
app:strokeWidth="1dp">
12+
13+
<LinearLayout
14+
android:layout_width="match_parent"
15+
android:layout_height="wrap_content"
16+
android:orientation="vertical">
17+
18+
<TextView
19+
android:layout_width="match_parent"
20+
android:layout_height="wrap_content"
21+
android:paddingStart="@dimen/spacing_lg"
22+
android:paddingTop="@dimen/spacing_md"
23+
android:paddingEnd="@dimen/spacing_lg"
24+
android:paddingBottom="@dimen/spacing_md"
25+
android:text="@string/about_title"
26+
android:textAppearance="@style/Text.MG4.SectionHeader" />
27+
28+
<View
29+
android:layout_width="match_parent"
30+
android:layout_height="1dp"
31+
android:background="@color/mg4_outline" />
32+
33+
<LinearLayout
34+
android:layout_width="match_parent"
35+
android:layout_height="wrap_content"
36+
android:baselineAligned="false"
37+
android:orientation="horizontal"
38+
android:padding="@dimen/spacing_lg">
39+
40+
<LinearLayout
41+
android:layout_width="0dp"
42+
android:layout_height="match_parent"
43+
android:layout_marginEnd="@dimen/spacing_lg"
44+
android:layout_weight="1"
45+
android:gravity="center_vertical"
46+
android:orientation="vertical">
47+
48+
<ImageView
49+
android:layout_width="128dp"
50+
android:layout_height="128dp"
51+
android:background="@drawable/about_logo_tile"
52+
android:contentDescription="@string/about_logo_description"
53+
android:padding="@dimen/spacing_sm"
54+
android:src="@drawable/ic_launcher_foreground" />
55+
56+
<TextView
57+
android:layout_width="match_parent"
58+
android:layout_height="wrap_content"
59+
android:layout_marginTop="@dimen/spacing_md"
60+
android:text="@string/app_name"
61+
android:textAppearance="@style/Text.MG4.Title" />
62+
63+
<TextView
64+
android:id="@+id/about_version"
65+
android:layout_width="match_parent"
66+
android:layout_height="wrap_content"
67+
android:layout_marginTop="@dimen/spacing_xs"
68+
android:textAppearance="@style/Text.MG4.Secondary" />
69+
70+
<TextView
71+
android:layout_width="match_parent"
72+
android:layout_height="wrap_content"
73+
android:layout_marginTop="@dimen/spacing_md"
74+
android:lineSpacingMultiplier="1.25"
75+
android:text="@string/about_attribution"
76+
android:textAppearance="@style/Text.MG4.Body" />
77+
</LinearLayout>
78+
79+
<View
80+
android:layout_width="1dp"
81+
android:layout_height="match_parent"
82+
android:layout_marginEnd="@dimen/spacing_lg"
83+
android:background="@color/mg4_outline" />
84+
85+
<LinearLayout
86+
android:layout_width="264dp"
87+
android:layout_height="wrap_content"
88+
android:gravity="center_horizontal"
89+
android:orientation="vertical">
90+
91+
<ImageView
92+
android:id="@+id/about_qr_code"
93+
android:layout_width="208dp"
94+
android:layout_height="208dp"
95+
android:background="#FFFFFFFF"
96+
android:contentDescription="@string/about_qr_description"
97+
android:padding="@dimen/spacing_sm"
98+
android:scaleType="fitCenter" />
99+
100+
<TextView
101+
android:id="@+id/about_repository"
102+
android:layout_width="match_parent"
103+
android:layout_height="@dimen/touch_target"
104+
android:layout_marginTop="@dimen/spacing_sm"
105+
android:clickable="true"
106+
android:focusable="true"
107+
android:gravity="center"
108+
android:text="@string/about_repository"
109+
android:textAppearance="@style/Text.MG4.Secondary"
110+
android:textColor="@color/mg4_accent" />
111+
</LinearLayout>
112+
</LinearLayout>
113+
114+
<View
115+
android:layout_width="match_parent"
116+
android:layout_height="1dp"
117+
android:background="@color/mg4_outline" />
118+
119+
<LinearLayout
120+
android:layout_width="match_parent"
121+
android:layout_height="wrap_content"
122+
android:gravity="center_vertical"
123+
android:orientation="horizontal"
124+
android:padding="@dimen/spacing_md">
125+
126+
<TextView
127+
android:layout_width="0dp"
128+
android:layout_height="wrap_content"
129+
android:layout_weight="1"
130+
android:text="@string/about_made_with_love"
131+
android:textAppearance="@style/Text.MG4.Body" />
132+
133+
<com.google.android.material.button.MaterialButton
134+
android:id="@+id/about_close"
135+
style="@style/Widget.MG4.Button.Outlined"
136+
android:layout_width="wrap_content"
137+
android:layout_height="wrap_content"
138+
android:minHeight="@dimen/touch_target"
139+
android:text="@string/about_close" />
140+
</LinearLayout>
141+
</LinearLayout>
142+
</com.google.android.material.card.MaterialCardView>

‎app/src/main/res/values/strings.xml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,14 @@
7474
<string name="notif_channel_desc">Shows vehicle telemetry upload status</string>
7575
<string name="notif_title">ABRP Uploader</string>
7676
<string name="notif_action_stop">Stop</string>
77+
<string name="about_action">About</string>
78+
<string name="about_title">About MG4 ABRP Uploader</string>
79+
<string name="about_version">Version %1$s</string>
80+
<string name="about_attribution">Maintained by malys\nFork of Leon Kernan / ABRP_Uploader</string>
81+
<string name="about_repository">github.com/malys/MG4AbrpUploader</string>
82+
<string name="about_made_with_love">Made with ❤ by malys</string>
83+
<string name="about_logo_description">MG4 ABRP Uploader logo</string>
84+
<string name="about_qr_description">QR code for the project repository</string>
85+
<string name="about_version_unknown">unknown</string>
86+
<string name="about_close">Close</string>
7787
</resources>

0 commit comments

Comments
 (0)