1+ /*
2+ * AppDataManager - Java Class for Android
3+ * Created by G.Capelli on 14/3/2026
4+ * This file is part of BasicAirData GPS Logger
5+ *
6+ * Copyright (C) 2011 BasicAirData
7+ *
8+ * This program is free software: you can redistribute it and/or modify
9+ * it under the terms of the GNU General Public License as published by
10+ * the Free Software Foundation, either version 3 of the License, or
11+ * (at your option) any later version.
12+ *
13+ * This program is distributed in the hope that it will be useful,
14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+ * GNU General Public License for more details.
17+ *
18+ * You should have received a copy of the GNU General Public License
19+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
20+ */
21+
22+ package eu .basicairdata .graziano .gpslogger ;
23+
24+ import android .net .Uri ;
25+ import android .util .Log ;
26+
27+ import androidx .documentfile .provider .DocumentFile ;
28+
29+ import org .jetbrains .annotations .Nullable ;
30+
31+ import java .io .BufferedInputStream ;
32+ import java .io .File ;
33+ import java .io .FileInputStream ;
34+ import java .io .FileNotFoundException ;
35+ import java .io .FileOutputStream ;
36+ import java .io .IOException ;
37+ import java .io .InputStream ;
38+ import java .io .OutputStream ;
39+ import java .text .SimpleDateFormat ;
40+ import java .util .Date ;
41+ import java .util .Locale ;
42+ import java .util .zip .ZipEntry ;
43+ import java .util .zip .ZipInputStream ;
44+ import java .util .zip .ZipOutputStream ;
45+
46+ /**
47+ * A class to manage some operations with the app data.
48+ * We use it for example to export and import the database of the tracks.
49+ */
50+ public class AppDataManager {
51+
52+ public boolean isLastOperationSuccessful = false ; // The outcome of the last operation carried out
53+
54+ private String appDataRootFolder = "/data/data/eu.basicairdata.graziano.gpslogger" ;
55+ private String zipFileFolder = GPSApplication .getInstance ().getPrefExportFolder ();
56+
57+
58+
59+ /**
60+ * It adds a specific folder recursively to a zip file.
61+ *
62+ * @param srcFolder The folder to add
63+ * @param destZipFile The zip file
64+ */
65+ private void zipFolder (String srcFolder , OutputStream destZipFile )
66+ throws Exception {
67+ ZipOutputStream zip = null ;
68+ OutputStream fileWriter = null ;
69+ fileWriter = destZipFile ;
70+ zip = new ZipOutputStream (fileWriter );
71+ addFolderToZip ("" , srcFolder , zip );
72+ zip .flush ();
73+ zip .close ();
74+ }
75+
76+ /**
77+ * It adds a specific file to a zip file.
78+ *
79+ * @param path The path of the file to add
80+ * @param srcFile The name of the file
81+ * @param zip The zip file
82+ */
83+ private void addFileToZip (String path , String srcFile ,
84+ ZipOutputStream zip ) throws Exception {
85+ File folder = new File (srcFile );
86+ if (folder .isDirectory ()) {
87+ addFolderToZip (path , srcFile , zip );
88+ } else {
89+ byte [] buf = new byte [1024 ];
90+ int len ;
91+ try {
92+ if ((srcFile .contains ("/databases/" ) || srcFile .contains ("/Thumbnails/" ))
93+ && !srcFile .endsWith ("-journal" ) && !srcFile .endsWith ("-shm" ) && !srcFile .endsWith ("-wal" )) {
94+ FileInputStream in = new FileInputStream (srcFile );
95+ zip .putNextEntry (new ZipEntry (path + "/" + folder .getName ()));
96+ Log .w ("myApp" , "[#] AppDataManager.java - Adding file " + path + "/" + folder .getName ());
97+ while ((len = in .read (buf )) > 0 ) {
98+ zip .write (buf , 0 , len );
99+ }
100+ if (srcFile .endsWith ("GPSLogger" )) {
101+ isLastOperationSuccessful = true ;
102+ Log .w ("myApp" , "[#] AppDataManager.java - The database \" GPSLogger\" has been successfully backupped" );
103+ }
104+ }
105+ } catch (FileNotFoundException e ) {
106+
107+ }
108+ }
109+ }
110+
111+ /**
112+ * It adds a specific folder to a zip file.
113+ *
114+ * @param path The path of the file to add
115+ * @param srcFolder The name of the file
116+ * @param zip The zip file
117+ */
118+ private void addFolderToZip (String path , String srcFolder ,
119+ ZipOutputStream zip ) throws Exception {
120+ // java.lang.RuntimeException: java.io.FileNotFoundException:
121+ // /data/data/eu.basicairdata.graziano.gpslogger/code_cache/.studio/.canary: open failed: EACCES (Permission denied)
122+ if (!srcFolder .endsWith ("/code_cache" ) && !srcFolder .endsWith ("/cache" )) {
123+ Log .w ("myApp" , "[#] AppDataManager.java - Adding folder " + srcFolder );
124+ File folder = new File (srcFolder );
125+ for (String fileName : folder .list ()) {
126+ if (path .equals ("" )) {
127+ addFileToZip (folder .getName (), srcFolder + "/" + fileName , zip );
128+ } else {
129+ addFileToZip (path + "/" + folder .getName (), srcFolder + "/" + fileName , zip );
130+ }
131+ }
132+ }
133+ }
134+
135+ /**
136+ * It exports the app data folder to a zip file into the exporting folder.
137+ * It creates a single zip file of the whole /data/data/eu.basicairdata.graziano.gpslogger folder.
138+ * This is the method used for Android API 14 to 18. API 19+ uses the other method:
139+ * exportAppDataToZipFile(Uri zipDocumentUri)
140+ */
141+ public void exportAppDataToZipFile_API14 () {
142+ isLastOperationSuccessful = false ;
143+ String backupFileName = "BACKUP GPSLogger Tracklist.zip" ;
144+ DocumentFile pickedDir ;
145+ pickedDir = DocumentFile .fromFile (new File (zipFileFolder ));
146+ DocumentFile zipDocumentFile = pickedDir .findFile (backupFileName );
147+ if ((zipDocumentFile != null ) && (zipDocumentFile .exists ())) zipDocumentFile .delete ();
148+ zipDocumentFile = pickedDir .createFile ("" , backupFileName );
149+
150+ try {
151+ OutputStream outputStream = GPSApplication .getInstance ().getContentResolver ().openOutputStream (zipDocumentFile .getUri (), "w" );
152+ zipFolder (appDataRootFolder , outputStream );
153+ } catch (FileNotFoundException e ) {
154+ throw new RuntimeException (e );
155+ } catch (NullPointerException e ) {
156+ Log .w ("myApp" , "[#] AppDataManager.java - UNABLE TO CREATE THE ZIP FILE into " + zipFileFolder );
157+ throw new RuntimeException (e );
158+ } catch (IOException e ) {
159+ throw new RuntimeException (e );
160+ } catch (Exception e ) {
161+ Log .w ("myApp" , "[#] AppDataManager.java - UNABLE TO CREATE THE ZIP FILE into" + zipFileFolder );
162+ throw new RuntimeException (e );
163+ }
164+ }
165+
166+ /**
167+ * It exports the app data folder to the zip file specified as parameter.
168+ * It creates a single zip file of the whole /data/data/eu.basicairdata.graziano.gpslogger folder.
169+ * * @param zipDocumentUri The Uri of the new ZIP file
170+ */
171+ public void exportAppDataToZipFile (Uri zipDocumentUri ) {
172+ isLastOperationSuccessful = false ;
173+
174+ try {
175+ DocumentFile zipDocumentFile ;
176+ zipDocumentFile = DocumentFile .fromSingleUri (GPSApplication .getInstance (), zipDocumentUri );
177+ OutputStream outputStream = GPSApplication .getInstance ().getContentResolver ().openOutputStream (zipDocumentFile .getUri (), "w" );
178+ zipFolder (appDataRootFolder , outputStream );
179+ } catch (FileNotFoundException e ) {
180+ throw new RuntimeException (e );
181+ } catch (NullPointerException e ) {
182+ Log .w ("myApp" , "[#] AppDataManager.java - UNABLE TO CREATE THE ZIP FILE " + zipDocumentUri .toString ());
183+ throw new RuntimeException (e );
184+ } catch (IOException e ) {
185+ throw new RuntimeException (e );
186+ } catch (Exception e ) {
187+ Log .w ("myApp" , "[#] AppDataManager.java - UNABLE TO CREATE THE ZIP FILE " + zipDocumentUri .toString ());
188+ throw new RuntimeException (e );
189+ }
190+ }
191+
192+ /**
193+ * It imports the Database and the Thumbnails from the zip file specified as parameter into the private data folder of the app.
194+ * * @param zipDocumentUri The Uri of the ZIP file
195+ */
196+ public void importTracklistFromZipFile (Uri zipDocumentUri ) {
197+ isLastOperationSuccessful = false ;
198+ try {
199+ DocumentFile zipDocumentFile = getDocumentFile (zipDocumentUri );
200+
201+ // Open InputStream from the DocumentFile
202+ InputStream inputStream = GPSApplication .getInstance ().getBaseContext ().getContentResolver ().openInputStream (zipDocumentFile .getUri ());
203+ ZipInputStream zipInputStream = new ZipInputStream (new BufferedInputStream (inputStream ));
204+
205+ ZipEntry ze = null ;
206+
207+ // Check if the archive contains the database and the thumbnails folder
208+ boolean isThumbnailsPresent = false ;
209+ boolean isDatabasePresent = false ;
210+ while ((ze = zipInputStream .getNextEntry ()) != null ) {
211+ if (ze .getName ().contains ("/Thumbnails/GPSLogger/" ) && !isThumbnailsPresent ) {
212+ Log .w ("myApp" , "[#] AppDataManager.java - Thumbnails folder found" );
213+ isThumbnailsPresent = true ;
214+ }
215+ if (ze .getName ().endsWith ("/GPSLogger" ) && !isDatabasePresent ) {
216+ Log .w ("myApp" , "[#] AppDataManager.java - Database found" );
217+ isDatabasePresent = true ;
218+ }
219+ }
220+ zipInputStream .close ();
221+ inputStream .close ();
222+
223+ // If the ZIP file is valid, restore the Tracklist files
224+ if (isDatabasePresent ) {
225+ inputStream = GPSApplication .getInstance ().getBaseContext ().getContentResolver ().openInputStream (zipDocumentFile .getUri ());
226+ zipInputStream = new ZipInputStream (new BufferedInputStream (inputStream ));
227+
228+ // Delete all the existing Thumbnails
229+ File folderThumbnails = new File ( GPSApplication .getInstance ().getTHUMBNAILS_FOLDER ());
230+ Log .w ("myApp" , "[#] AppDataManager.java - Thumbnail Folder: " + folderThumbnails .getPath ());
231+ if (folderThumbnails .isDirectory ())
232+ {
233+ String [] children = folderThumbnails .list ();
234+ for (int i = 0 ; i < children .length ; i ++)
235+ {
236+ new File (folderThumbnails , children [i ]).delete ();
237+ }
238+ }
239+
240+ // Delete the existing Database
241+ File folderDatabase = new File (GPSApplication .getInstance ().getDatabasePath ("GPSLogger" ).getParent ());
242+ Log .w ("myApp" , "[#] AppDataManager.java - Databases Folder: " + folderDatabase .getPath ());
243+ if (folderDatabase .isDirectory ())
244+ {
245+ String [] children = folderDatabase .list ();
246+ for (int i = 0 ; i < children .length ; i ++)
247+ {
248+ new File (folderDatabase , children [i ]).delete ();
249+ }
250+ }
251+
252+ // the ZIP file is valid
253+ Log .w ("myApp" , "[#] AppDataManager.java - The ZIP file is valid, Restoring..." );
254+ while ((ze = zipInputStream .getNextEntry ()) != null ) {
255+ // Import a Thumbnail
256+ if (ze .getName ().contains ("/Thumbnails/GPSLogger" )) {
257+ Log .w ("myApp" , "[#] AppDataManager.java - UNZIP Thumbnail: " + ze .getName ().substring (ze .getName ().lastIndexOf ("/" ) + 1 ));
258+ byte [] buffer = new byte [1024 ];
259+ FileOutputStream fout = new FileOutputStream (folderThumbnails + "/" + ze .getName ().substring (ze .getName ().lastIndexOf ("/" ) + 1 ));
260+ int i = 0 ;
261+ int len ;
262+ while ((len = zipInputStream .read (buffer )) > 0 ) {
263+ fout .write (buffer , 0 , len );
264+ }
265+ zipInputStream .closeEntry ();
266+ fout .close ();
267+ }
268+ // Import the Database
269+ if (ze .getName ().endsWith ("/GPSLogger" )) {
270+ Log .w ("myApp" , "[#] AppDataManager.java - UNZIP Database: " + ze .getName ().substring (ze .getName ().lastIndexOf ("/" ) + 1 ));
271+ byte [] buffer = new byte [1024 ];
272+ FileOutputStream fout = new FileOutputStream (folderDatabase + "/" + ze .getName ().substring (ze .getName ().lastIndexOf ("/" ) + 1 ));
273+ int i = 0 ;
274+ int len ;
275+ while ((len = zipInputStream .read (buffer )) > 0 ) {
276+ fout .write (buffer , 0 , len );
277+ }
278+ zipInputStream .closeEntry ();
279+ fout .close ();
280+ isLastOperationSuccessful = true ;
281+ }
282+ }
283+ zipInputStream .close ();
284+ inputStream .close ();
285+
286+ // Update the signature to invalidate the Glide cache
287+ SimpleDateFormat sdf = new SimpleDateFormat ("yyyyMMdd-HHmmss" , Locale .US );
288+ GPSApplication .getInstance ().setTracklistSignature (sdf .format (new Date ()));
289+ Log .w ("myApp" , "[#] AppDataManager.java - New signature for Thumbnails: " + GPSApplication .getInstance ().getTracklistSignature ());
290+ }
291+ } catch (Exception e ) {
292+ System .out .println (e );
293+ }
294+ }
295+
296+ @ Nullable
297+ private static DocumentFile getDocumentFile (Uri zipDocumentUri ) {
298+ DocumentFile zipDocumentFile ;
299+ zipDocumentFile = DocumentFile .fromSingleUri (GPSApplication .getInstance (), zipDocumentUri );
300+ return zipDocumentFile ;
301+ }
302+ }
0 commit comments