Skip to content

Commit baa9d1b

Browse files
committed
Beef Launcher & Stats Update
Main Window - Improved overall visual appearance and UI polish - Added an "All Games Usage Stats" button --- App Details - Redesigned the App Details view - Added a Performance button to open the Pico Performance Center (Pico 4 Ultra only) - Added a "Per Game Usage Stats" button - Added version code and install date next to the app version --- Other Changes - Fixed the startup animation for 2D apps - Added a setting to preserve icon aspect ratios - Added mini launchers for Team Beef Games
1 parent d255f4c commit baa9d1b

62 files changed

Lines changed: 8980 additions & 211 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AppDetails.jpeg

3.6 KB
Loading

CHANGELOG.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
# Latest version: [PiLauncher_2.1.apk](https://github.com/Tobbe85/PiLauncher/releases/download/2.1/PiLauncher_2.1.apk)
1+
# Latest version: [PiLauncher_2.3.apk](https://github.com/Tobbe85/PiLauncher/releases/download/2.3/PiLauncher_2.3.apk)
22

33
## Changelog
44

5-
**v2.1**
6-
- Now the Default Style matches the Pico Style on Pico
7-
- Engine Text got an own row in Detail App View
8-
- Improved Settings Symbol in Main View
9-
- In PiLauncher Settings you can now choose the Mode (Docked / Windowed) on Pico
10-
- Updates "Available Storage" Text directly after deleting an app
11-
- Click on the "Available Storage" Info opens the Storage Manager
12-
- Added a Battery Indicator on bottom row
13-
- Long Press on Main View activates or deactivates "Edit Mode"
14-
- Added an own WebView instead of open the Browser for Game Information
15-
- Start Animation by clicking on an app (Zoom & Fade)
16-
- Icons will be stored in /data/data/com.tobbe.pilauncher/files now
5+
**v2.3**
6+
7+
## Main Window
8+
- Improved overall visual appearance and UI polish
9+
- **Added an "All Games Usage Stats" button**
10+
---
11+
## App Details
12+
- Redesigned the **App Details** view
13+
- Added a **Performance** button to open the Pico Performance Center *(Pico 4 Ultra only)*
14+
- **Added a "Per Game Usage Stats" button**
15+
- Added version code and install date next to the app version
16+
---
17+
## Other Changes
18+
- Fixed the startup animation for 2D apps
19+
- Added a setting to preserve icon aspect ratios
20+
- **Added mini launchers for (Team Beef):**
21+
- **JKXR**
22+
- **Lambda1VR**
23+
- **RazeXR**
24+
- **QuestZDoom**

Launcher/App/build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ android {
1818
compileSdkVersion 29
1919

2020
defaultConfig {
21-
minSdkVersion 23
21+
minSdkVersion 24
2222
//noinspection ExpiredTargetSdkVersion
23-
targetSdkVersion 23
24-
versionCode 30
25-
versionName "2.1"
23+
targetSdkVersion 24
24+
versionCode 38
25+
versionName "2.3"
2626
signingConfig signingConfigs.release
2727
}
2828

Launcher/App/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
88
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
99
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions"/>
10+
<uses-permission android:name="android.permission.INTERNET" />
1011

1112
<application
1213
android:icon="@drawable/ic_launcher"
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package com.tobbe.pilauncher;
2+
3+
import android.content.Context;
4+
import android.net.Uri;
5+
6+
import java.io.*;
7+
import java.nio.charset.StandardCharsets;
8+
import java.util.zip.ZipEntry;
9+
import java.util.zip.ZipInputStream;
10+
import java.util.zip.ZipOutputStream;
11+
12+
public class BackupRestoreManager {
13+
14+
private static final String META_FILE = "backup.meta";
15+
private static final String BACKUP_VERSION = "1";
16+
private static final int BUFFER_SIZE = 8192;
17+
18+
public static void backupAppData(Context context, Uri uri) throws IOException {
19+
20+
File dataDir = context.getDataDir();
21+
22+
try (
23+
OutputStream os = context.getContentResolver().openOutputStream(uri);
24+
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os))
25+
) {
26+
writeMetaFile(context, zos);
27+
zipFolder(dataDir, dataDir, zos);
28+
}
29+
}
30+
31+
private static void writeMetaFile(Context context, ZipOutputStream zos) throws IOException {
32+
33+
ZipEntry metaEntry = new ZipEntry(META_FILE);
34+
zos.putNextEntry(metaEntry);
35+
36+
String content =
37+
"app=" + context.getPackageName() + "\n" +
38+
"version=" + BACKUP_VERSION + "\n";
39+
40+
zos.write(content.getBytes(StandardCharsets.UTF_8));
41+
zos.closeEntry();
42+
}
43+
44+
private static void zipFolder(File rootDir, File currentDir, ZipOutputStream zos) throws IOException {
45+
46+
File[] files = currentDir.listFiles();
47+
48+
if (files == null) return;
49+
50+
byte[] buffer = new byte[BUFFER_SIZE];
51+
52+
for (File file : files) {
53+
54+
String relativePath =
55+
file.getAbsolutePath()
56+
.substring(rootDir.getAbsolutePath().length() + 1);
57+
58+
if (file.isDirectory()) {
59+
60+
zipFolder(rootDir, file, zos);
61+
62+
} else {
63+
64+
try (FileInputStream fis = new FileInputStream(file)) {
65+
66+
ZipEntry entry = new ZipEntry(relativePath);
67+
zos.putNextEntry(entry);
68+
69+
int len;
70+
71+
while ((len = fis.read(buffer)) > 0) {
72+
zos.write(buffer, 0, len);
73+
}
74+
75+
zos.closeEntry();
76+
}
77+
}
78+
}
79+
}
80+
81+
public static void restoreAppData(Context context, Uri uri) throws IOException {
82+
83+
validateBackupFile(context, uri);
84+
restoreBackupFile(context, uri);
85+
}
86+
87+
private static void validateBackupFile(Context context, Uri uri) throws IOException {
88+
89+
boolean validMetaFound = false;
90+
91+
try (
92+
InputStream is = context.getContentResolver().openInputStream(uri);
93+
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is))
94+
) {
95+
ZipEntry entry;
96+
byte[] buffer = new byte[BUFFER_SIZE];
97+
98+
while ((entry = zis.getNextEntry()) != null) {
99+
100+
String entryName = entry.getName();
101+
102+
validateZipEntryName(context, entryName);
103+
104+
if (META_FILE.equals(entryName)) {
105+
106+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
107+
108+
int len;
109+
110+
while ((len = zis.read(buffer)) > 0) {
111+
bos.write(buffer, 0, len);
112+
}
113+
114+
String meta = bos.toString(StandardCharsets.UTF_8.name());
115+
116+
boolean correctApp =
117+
meta.contains("app=" + context.getPackageName());
118+
119+
boolean correctVersion =
120+
meta.contains("version=" + BACKUP_VERSION);
121+
122+
if (correctApp && correctVersion) {
123+
validMetaFound = true;
124+
}
125+
}
126+
127+
zis.closeEntry();
128+
}
129+
}
130+
131+
if (!validMetaFound) {
132+
throw new SecurityException(context.getString(R.string.backup_invalid));
133+
}
134+
}
135+
136+
private static void restoreBackupFile(Context context, Uri uri) throws IOException {
137+
138+
File dataDir = context.getDataDir();
139+
140+
String canonicalTarget =
141+
dataDir.getCanonicalPath() + File.separator;
142+
143+
try (
144+
InputStream is = context.getContentResolver().openInputStream(uri);
145+
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is))
146+
) {
147+
ZipEntry entry;
148+
byte[] buffer = new byte[BUFFER_SIZE];
149+
150+
while ((entry = zis.getNextEntry()) != null) {
151+
152+
String entryName = entry.getName();
153+
154+
validateZipEntryName(context, entryName);
155+
156+
if (META_FILE.equals(entryName)) {
157+
zis.closeEntry();
158+
continue;
159+
}
160+
161+
File outFile = new File(dataDir, entryName);
162+
String canonicalOut = outFile.getCanonicalPath();
163+
164+
if (!canonicalOut.startsWith(canonicalTarget)) {
165+
throw new SecurityException(context.getString(R.string.backup_traversal));
166+
}
167+
168+
if (entry.isDirectory()) {
169+
170+
if (!outFile.exists() && !outFile.mkdirs()) {
171+
throw new IOException(context.getString(R.string.backup_couldnot) + outFile);
172+
}
173+
174+
} else {
175+
176+
File parent = outFile.getParentFile();
177+
178+
if (parent != null && !parent.exists()) {
179+
if (!parent.mkdirs()) {
180+
throw new IOException(context.getString(R.string.backup_couldnot) + parent);
181+
}
182+
}
183+
184+
try (FileOutputStream fos = new FileOutputStream(outFile)) {
185+
186+
int len;
187+
188+
while ((len = zis.read(buffer)) > 0) {
189+
fos.write(buffer, 0, len);
190+
}
191+
}
192+
}
193+
194+
zis.closeEntry();
195+
}
196+
}
197+
}
198+
199+
private static void validateZipEntryName(Context context, String entryName) {
200+
201+
if (entryName == null ||
202+
entryName.trim().isEmpty() ||
203+
entryName.startsWith("/") ||
204+
entryName.startsWith("\\") ||
205+
entryName.contains("..") ||
206+
entryName.contains(":")) {
207+
208+
throw new SecurityException(context.getString(R.string.backup_invalidzip));
209+
}
210+
}
211+
}

0 commit comments

Comments
 (0)